/* Home intro, scene B · Index (js/intro-gate.js → js/intro-core.js → js/intro-story.js).
   Hidden unless the gate set html.intro-on before first paint; the markup keeps [hidden] for
   crawlers, no-JS and the md mirror. Everything inside .intro-scene is built and moved by
   intro-story.js; nothing it animates is centred with the translate property. */
.intro-story { display: none; }
html.intro-on .intro-story { display: block; z-index: 5; pointer-events: none; }
/* Scroll version: the 100svh stage + 390svh of scene + a 50svh hold (HOLD in intro-story.js), in
   which the stage stays pinned while the eased progress finishes the landing. */
html[data-intro="scroll"] .intro-story { position: relative; height: 540svh; }
html[data-intro="auto"] .intro-story { position: fixed; inset: 0; height: 100svh; }
.intro-stage { position: sticky; top: 0; height: 100svh; overflow: hidden; isolation: isolate; }
.intro-backdrop { position: absolute; inset: 0; background: var(--bg); }
.intro-backdrop::after { content: ""; position: absolute; inset: 0; background-image: var(--grain); background-size: 140px; opacity: .06; mix-blend-mode: overlay; }
/* Opaque, so it takes the pointer: taps land on the intro, never on links beneath it. */
html.intro-on .intro-backdrop { pointer-events: auto; }
.intro-glow { position: absolute; left: 50%; top: 50%; width: 84vmin; height: 84vmin; margin: -42vmin 0 0 -42vmin; border-radius: 50%; opacity: 0;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--text) 6%, transparent), transparent 72%); }
.intro-scene { position: absolute; inset: 0; }
/* The parts that move or fade every frame get their own layers, so a frame is a composite, not a
   repaint of the stage. The mark is one already (its perspective()); nothing here scales past 1
   except for a brief icon pulse, so no layer is drawn blurry. The headline's flying words get
   none: they grow onto the h1, and a layer would keep their small raster. */
.intro-row, .intro-title, .intro-rule, .intro-wordmark, .intro-glow { will-change: transform, opacity; }

/* Opening: the wordmark, whose dot travels to become the mark's dot */
.intro-wordmark { position: absolute; inset: 0; display: grid; place-items: center; }
.intro-wordmark svg { display: block; width: min(62vw, 900px); height: auto; overflow: visible; }
.intro-wordmark .logo-word { fill: var(--text); }
.intro-wordmark .logo-mark { fill: var(--logo-mark); opacity: 0; }
.intro-dot { position: absolute; left: 0; top: 0; border-radius: 50%; background: var(--logo-mark); }

/* The mark: the hero sculpture's four pieces (cloned; styles/hero-art.css colours them by
   data-part) stacked on one box, so each part can move on its own */
.intro-mark { --S: min(34vmin, 320px); position: absolute; left: 50%; top: 50%; width: var(--S); aspect-ratio: 260 / 250; margin: calc(var(--S) * -125 / 260) 0 0 calc(var(--S) / -2); }
.intro-piece { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; transform-origin: 0 0; }
/* The sculpture's drawn trace (js/hero-callouts.js) comes along in the clones; it is the hero's. */
.intro-piece .brand-trace { display: none; }

/* The index: each part heads its column, the solutions beneath it */
.intro-index { position: absolute; left: 50%; top: 52%; translate: -50% -50%; width: min(1180px, 88vw); display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: clamp(28px, 3.4vw, 56px); align-items: start; }
.intro-col { position: relative; }
.intro-head { display: flex; align-items: center; gap: 14px; height: 40px; }
.intro-glyph { flex: none; width: 34px; height: 34px; }
.intro-title { color: var(--text); font: 500 21px/1.2 var(--font-display); letter-spacing: -.015em; }
.intro-rule { display: block; height: 1px; margin: 18px 0 8px; background: var(--border-strong); transform-origin: left center; }
.intro-rows { margin: 0; padding: 0; list-style: none; }
.intro-row { position: relative; display: flex; align-items: flex-start; gap: 12px; padding: 13px 0; transform-origin: 50% 0; }
.intro-icon { flex: none; width: 19px; height: 19px; margin-top: 1px; color: var(--accent-soft); }
.intro-icon svg { display: block; width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.intro-text { position: relative; flex: 1 1 auto; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.intro-name { color: var(--text); font: 500 15.5px/1.3 var(--font-body); }
.intro-cap { color: var(--text-muted); font: 400 12.5px/1.35 var(--font-body); }

/* The request above the index, "We need …": its chip cycles the asks, one per part, and ends holding
   the headline, whose words then fly onto the h1 (intro-story.js). */
.intro-prompt { position: absolute; left: 0; top: 0; display: flex; flex-wrap: wrap; align-items: center; column-gap: .3em; row-gap: 6px; opacity: 0;
  color: var(--text); font: 500 26px/1.25 var(--font-display); letter-spacing: -.015em; white-space: nowrap; }
.intro-typed { display: block; }
.intro-typed:empty { display: none; }
/* The chip's text is --accent-text, readable at phone size in every concept (in A it is --accent-soft;
   --accent-soft is 2.4:1 on C's page); its pill is drawn in the wordmark dot's colour. */
.intro-chip { position: relative; flex: none; display: inline-flex; align-items: center; height: 1.55em; padding: 0 .5em; overflow: hidden; box-sizing: border-box; color: var(--accent-text); }
.intro-pill { position: absolute; inset: 0; border: 1px solid color-mix(in srgb, var(--logo-mark) 45%, var(--border)); border-radius: .5em; background: color-mix(in srgb, var(--logo-mark) 8%, var(--bg)); }
/* one line while the chip eases to the next width, also on a phone */
.intro-chip-in { position: relative; display: block; white-space: nowrap; }
.intro-caret { display: inline-block; width: 2px; height: 1.05em; margin-left: .08em; background: var(--text); animation: intro-blink 1s steps(1) infinite; }
.intro-caret[hidden] { display: none; }
@keyframes intro-blink { 50% { opacity: 0; } }
.intro-measure { position: absolute; visibility: hidden; left: -9999px; top: 0; white-space: nowrap; font: 500 26px/1.25 var(--font-display); letter-spacing: -.015em; }
/* The headline's words, set at the headline's size and scaled down onto the request, so they end
   crisp and exactly on the headline's glyphs. No layer of their own (see above). */
.intro-word { position: absolute; left: 0; top: 0; margin: 0; white-space: pre; line-height: normal; transform-origin: 0 0; opacity: 0; color: var(--text); pointer-events: none; }

/* The answers: a soft light passes over the row that answers, and its result takes the caption's
   line (the name's where the caption is hidden), counted up, violet while it is the answer. */
.intro-row::after { content: ""; position: absolute; inset: 3px -8px; border-radius: 6px; pointer-events: none;
  background: linear-gradient(100deg, transparent 38%, color-mix(in srgb, var(--text) 10%, transparent) 50%, transparent 62%);
  background-size: 300% 100%; background-position: var(--shine, 100%) 0; }
/* The label is the page's own wording, never cut: it may take a second line beside the number. */
.intro-metric { position: absolute; left: 0; right: 0; top: 0; display: flex; align-items: baseline; gap: 8px; min-width: 0; opacity: 0; pointer-events: none; }
.intro-metric b { flex: none; font: 600 clamp(22px, 1.8vw, 26px)/1 var(--font-display); letter-spacing: -.035em; font-variant-numeric: tabular-nums; color: var(--text); white-space: nowrap; }
.intro-metric span { min-width: 0; color: var(--text-muted); font: 400 12.5px/1.2 var(--font-body); text-wrap: balance; }

/* "Scroll to explore" and "Skip intro". Every 3.2 s the hint hops twice, high then low, its arrow
   following 80 ms behind, and as it lands a soft light crosses its words (the same light that
   crosses an answering row); the Skip label gets the light from 1.2 s every 6 s. Both stop once the
   scroll version is under way (html.intro-moved, intro-story.js). */
.intro-hint { position: absolute; bottom: 26px; left: 0; width: 100%; margin: 0; display: flex; flex-direction: column; align-items: center; gap: 8px;
  color: var(--text-muted); font: 500 14px/1.2 var(--font-display); letter-spacing: -.005em; }
.intro-shine { background: linear-gradient(100deg, var(--text-muted) 35%, var(--text) 50%, var(--text-muted) 65%) 0 0 / 300% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent; }
.intro-hint .intro-shine { animation: intro-hop 3.2s .4s infinite, intro-shine 3.2s var(--ease-in-out) .4s infinite; }
.intro-skip .intro-shine { animation: intro-shine-skip 6s var(--ease-in-out) 1.2s infinite; }
@keyframes intro-shine { 0%, 22% { background-position: 100% 0; } 64%, 100% { background-position: 0 0; } }
@keyframes intro-shine-skip { 0% { background-position: 100% 0; } 14%, 100% { background-position: 0 0; } }
/* up decelerating, down accelerating, like a thrown thing: 12px, then 5px, then rest */
@keyframes intro-hop {
  0% { translate: 0 0; animation-timing-function: cubic-bezier(.33, 1, .68, 1); }
  7% { translate: 0 -12px; animation-timing-function: cubic-bezier(.32, 0, .67, 0); }
  14% { translate: 0 0; animation-timing-function: cubic-bezier(.33, 1, .68, 1); }
  18.5% { translate: 0 -5px; animation-timing-function: cubic-bezier(.32, 0, .67, 0); }
  23%, 100% { translate: 0 0; } }
.intro-chev { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
  animation: intro-hop 3.2s .48s infinite; }
html.intro-moved .intro-shine, html.intro-moved .intro-chev { animation: none; }
.intro-skip { position: absolute; top: 28px; right: var(--gutter); min-height: 44px; padding: 10px 16px; overflow: hidden; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text-muted); font: 400 12px var(--font-body); pointer-events: auto; cursor: pointer; }
.intro-skip:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: 4px; }
/* The Skip button's own line: how far the intro has run (scaleX from intro-story.js) */
.intro-skip-progress { position: absolute; left: 12px; right: 12px; bottom: 7px; height: 1px; background: var(--text-muted); opacity: .55; transform: scaleX(0); transform-origin: left center; }

/* The page around the intro */
/* Scroll version: the hero waits, fixed, exactly where it will be once finish() collapses the
   section, so the landing mark and the sculpture beneath it never drift apart. */
html[data-intro="scroll"] .hero { position: fixed; top: 0; left: 0; right: 0; }
/* body is overflow-x: hidden at every width (base.css); at ≤1080px components.css adds it on html
   too, so body's overflow stops propagating to the viewport, body becomes a scroll container and
   the stage unsticks. clip trims the same overflow without making a scroll container. */
html[data-intro="scroll"] body { overflow-x: clip; }
html[data-intro="auto"] .intro-hint { display: none; }
/* Nothing under the backdrop is clickable — also before intro-story.js makes it inert. */
html.intro-on .hero { pointer-events: none; }
/* The header fades in on landing; its opacity transition is its own (components.css), so the fade
   runs on after html.intro-on goes at the finish. */
html.intro-on:not(.intro-landed) .site-header { opacity: 0; pointer-events: none; }
/* The cookie question waits for the intro (js/consent.js, zv:intro-done); so does its button. Back to
   top (js/shell.js, from 0.9 viewport down) would sit over the scroll version and rewind it. */
html.intro-on .cookie-fab, html.intro-on .to-top { visibility: hidden; }

@media (max-width: 860px) {
  .intro-mark { --S: min(58vw, 300px); }
  .intro-wordmark svg { width: 84vw; }
  .intro-index { top: 50%; width: calc(100vw - 32px); grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 26px; column-gap: 16px; }
  .intro-head { height: 30px; gap: 10px; }
  .intro-glyph { width: 26px; height: 26px; }
  .intro-title { font-size: 16px; }
  .intro-rule { margin: 12px 0 4px; }
  .intro-row { padding: 8px 0; gap: 9px; }
  .intro-name { font-size: 13px; }
  .intro-cap { display: none; }
  .intro-icon { width: 16px; height: 16px; }
  .intro-hint { bottom: 16px; font-size: 13px; }
  .intro-prompt, .intro-measure { font-size: 17px; }
  .intro-prompt { white-space: normal; max-width: calc(100vw - 32px); }
  .intro-metric { gap: 6px; }
  .intro-metric b { font-size: 15px; letter-spacing: -.02em; }
  .intro-metric span { font-size: 11px; }
}
/* Narrow phones (360px): the longest ask keeps the chip inside the screen's margin. */
@media (max-width: 380px) {
  .intro-prompt, .intro-measure { font-size: 16px; }
}
/* Short landscape screens (phones on their side): one row of four compact columns. */
@media (max-height: 600px) and (orientation: landscape) {
  .intro-index { top: 54%; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 16px; row-gap: 0; }
  .intro-head { height: 26px; gap: 8px; }
  .intro-glyph { width: 22px; height: 22px; }
  .intro-title { font-size: 14px; }
  .intro-rule { margin: 8px 0 2px; }
  .intro-row { padding: 5px 0; gap: 8px; }
  .intro-name { font-size: 12px; }
  .intro-cap { display: none; }
  .intro-icon { width: 14px; height: 14px; }
  .intro-hint { bottom: 8px; gap: 4px; }
  .intro-prompt, .intro-measure { font-size: 16px; }
  .intro-metric { gap: 6px; }
  .intro-metric b { font-size: 14px; letter-spacing: -.02em; }
  /* a page's longer result label may take two lines here: tight lines, so two of them clear the
     row below (at 11px, the canon's floor) */
  .intro-metric span { font-size: 11px; line-height: 1; }
}
@media (prefers-reduced-motion: reduce) {
  html.intro-on .intro-story { display: none; }
  .intro-caret, .intro-shine, .intro-chev { animation: none; }
  html[data-intro="scroll"] .hero { position: relative; }
  html.intro-on .hero, html.intro-on:not(.intro-landed) .site-header { opacity: 1; pointer-events: auto; }
  html.intro-on .cookie-fab, html.intro-on .to-top { visibility: visible; }
}
