/*
 * Landing page — scroll-driven.
 *
 * ── The architecture, because it is the whole point ─────────────────────────
 *
 * `landing.js` writes three kinds of custom property and nothing else:
 *
 *   --p        0 → 1   progress through a scene
 *   --s1..--sN 0 → 1   progress through each of the scene's declared stages
 *   --stage    0 → N   the same position as a continuous float, for morphing
 *
 * Every transform, opacity and colour below is `calc()` off those numbers. No
 * keyframes drive the narrative, and no class is toggled to "play" anything. Two
 * consequences worth the constraint: scrolling backwards reverses everything
 * exactly, because the value is a pure function of scroll position; and the
 * effects are declared here next to the layout they belong to rather than as a
 * pile of magic numbers in a script.
 *
 * Staggering inside a stage uses `--i` on the child and a shared clamp, so a
 * sequence of four things is four indices rather than four hand-written delays.
 *
 * ── Why no GSAP ────────────────────────────────────────────────────────────
 *
 * The platform ships zero runtime dependencies and is served under
 * `default-src 'self'`, so a CDN script is blocked at the browser and a vendored
 * one breaks the rule the whole product is sold on. Everything here is transform
 * and opacity on compositor-friendly properties, which is what a library would
 * have produced anyway.
 *
 * Tokens are copied from `styles.css` rather than imported: the application
 * stylesheet is two thousand lines of authenticated screens, and it is digested
 * by the rendered accessibility audit, so folding landing styles into it would
 * invalidate that evidence on every margin change here.
 */

@layer tokens, base, chrome, scenes, motion, adapt, components;

@layer tokens {
  :root {
    color-scheme: dark;

    /* Brand, from apps/web/styles.css. */
    --navy-950: #081a2b;
    --navy-900: #0b2239;
    --teal-700: #006c70;
    --teal-600: #007c80;
    --teal-200: #9adfd6;
    --teal-100: #c7eee8;
    --slate-200: #d7e0e5;

    /* Landing-only environment colours. */
    --ink-abyss: #04090f;
    --ink-deep: #071320;
    --ink-mid: #0b2239;
    --paper: #f4f8fa;
    --paper-ink: #0d1c28;

    --fg: #eef5f8;
    --fg-dim: #9fb4c2;
    /* Raised from #5d7686, which measured 2.15:1 against the figures panel — small
       supporting text, so it needs 4.5:1 and was nowhere near it. */
    --fg-faint: #93a8b6;
    --hair: #ffffff1f;
    --hair-strong: #ffffff33;

    --accent: var(--teal-200);
    --accent-deep: var(--teal-700);
    --focus: #73e0d1;

    --font:
      Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
      Roboto, Helvetica, Arial, sans-serif;
    --mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;

    --shell: 78rem;
    --measure: 42rem;
    --gutter: clamp(1.25rem, 4.5vw, 4rem);
    --target: 2.75rem;

    --r-sm: 0.5rem;
    --r-md: 0.875rem;
    --r-lg: 1.25rem;
    --r-pill: 999rem;

    --t-1: 0.8125rem;
    --t-2: 0.9375rem;
    --t-3: 1.0625rem;
    --t-4: clamp(1.25rem, 1.05rem + 0.9vw, 1.75rem);
    --t-5: clamp(1.75rem, 1.35rem + 1.9vw, 2.75rem);
    --t-6: clamp(2.25rem, 1.5rem + 3.6vw, 4.25rem);
    --t-7: clamp(2.75rem, 1.2rem + 7vw, 7rem);

    --ease: cubic-bezier(0.22, 0.61, 0.24, 1);
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

    /* Defaults so the page is fully composed before the engine's first frame,
       and stays composed if it never runs. */
    --p: 0;
    --s1: 1;
    --s2: 1;
    --s3: 1;
    --s4: 1;
    --s5: 1;
    --stage: 0;
    --doc: 0;
    --mx: 0;
    --my: 0;
    --tx: 0;
    --ty: 0;
  }
}

@layer base {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  html {
    scroll-behavior: smooth;
    scroll-padding-block-start: 6rem;
    background: var(--ink-abyss);
  }

  body.landing {
    margin: 0;
    font-family: var(--font);
    font-size: var(--t-3);
    line-height: 1.6;
    color: var(--fg);
    background: var(--ink-abyss);
    -webkit-font-smoothing: antialiased;
    /* The horizontal rail translates a wide track. Without this a sub-pixel
       overhang gives the whole document a horizontal scrollbar. */
    overflow-x: clip;
  }

  h1, h2, h3 {
    margin: 0;
    line-height: 1.1;
    letter-spacing: -0.025em;
    font-weight: 700;
    text-wrap: balance;
  }

  h1 { font-size: var(--t-6); }
  h2 { font-size: var(--t-5); }
  h3 { font-size: var(--t-4); letter-spacing: -0.015em; }

  p { margin: 0; text-wrap: pretty; }
  ul, ol { margin: 0; padding: 0; list-style: none; }
  a { color: inherit; text-decoration: none; }

  :focus-visible {
    outline: 3px solid var(--focus);
    outline-offset: 3px;
    border-radius: 3px;
  }

  .skip-link {
    position: fixed;
    inset-block-start: 0;
    inset-inline-start: 0;
    z-index: 200;
    padding: 0.875rem 1.25rem;
    background: var(--paper);
    color: var(--paper-ink);
    font-weight: 600;
    /* Moved rather than hidden: display:none is not focusable, and a skip link
       that cannot take focus is decoration. */
    transform: translateY(-130%);
  }

  .skip-link:focus-visible { transform: none; }

  .eyebrow {
    margin: 0 0 1.125rem;
    font-size: var(--t-1);
    font-weight: 600;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--accent);
  }

  .lead { font-size: var(--t-4); color: var(--fg-dim); line-height: 1.45; }

  .measure { max-inline-size: var(--measure); min-inline-size: 0; }

  section { position: relative; }
}

/* ── page chrome ──────────────────────────────────────────────────────────── */

@layer chrome {
  .nav {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1.375rem var(--gutter);
    color: var(--fg);
    transition:
      padding 420ms var(--ease),
      background-color 420ms var(--ease),
      border-color 420ms var(--ease),
      backdrop-filter 420ms var(--ease);
    border-block-end: 1px solid transparent;
  }

  /* Compact state. A container that shrinks and gains a translucent floor,
     rather than a second bar that appears. */
  .nav.is-compact {
    padding-block: 0.6875rem;
    background: #061019cc;
    backdrop-filter: blur(14px) saturate(140%);
    border-block-end-color: var(--hair);
  }

  /* Parking space for the inline `<symbol>` in index.html. Not `display: none`: a `<symbol>` inside
     a `display: none` `<svg>` does resolve in every current browser, but a zero-size clipped box has
     no history of breaking anywhere, and it costs one rule. */
  .sprite {
    position: absolute;
    inline-size: 0;
    block-size: 0;
    overflow: hidden;
  }

  .nav__mark {
    display: inline-flex;
    align-items: center;
  }

  /* The real lockup, at a size that clears both floors in BRAND.md §3.5. The horizontal lockup's
     floor is 120px wide; the mark's own floor is 24px, and the mark inside this artwork is 77 of the
     lockup's 443.87 units, so 139px puts it at 24.11px. Measured on #04090F: at 139px the mark's
     15/245 gutters hold one device pixel of pure ground at 1x and two at 2x, where the 14px gradient
     square they replace held none at 1x.

     `max()` because the 24px floor is geometric, not a preference: a visitor whose root font-size is
     below 16px must not push the mark under it, while one above 16px should still get a larger
     lockup. `aspect-ratio` rather than a height, because the outer `<svg>` has no viewBox of its own
     — the `<symbol>` carries it — so it has no intrinsic ratio to reserve space with. Sizing one axis
     and deriving the other also makes stretching, which §3.5 forbids, unreachable. */
  .nav__lockup {
    display: block;
    inline-size: max(139px, 8.6875rem);
    block-size: auto;
    aspect-ratio: 443.87 / 91.72;
  }

  .nav__links { margin-inline: auto; }
  .nav__links ul { display: flex; gap: 0.125rem; }

  .nav__links a {
    position: relative;
    display: inline-flex;
    align-items: center;
    min-block-size: var(--target);
    padding-inline: 0.8125rem;
    font-size: var(--t-2);
    font-weight: 600;
    color: var(--fg-dim);
    transition: color 240ms var(--ease);
  }

  .nav__links a:hover { color: var(--fg); }

  /* Masked underline: a scaled line, so no layout is touched on hover. */
  .nav__links a::after {
    content: "";
    position: absolute;
    inset-inline: 0.8125rem;
    inset-block-end: 0.55rem;
    block-size: 1px;
    background: currentcolor;
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 320ms var(--ease-out);
  }

  .nav__links a:hover::after { transform: scaleX(1); }

  .nav__actions { display: flex; align-items: center; gap: 0.75rem; }

  .link-quiet {
    font-size: var(--t-2);
    font-weight: 600;
    color: var(--fg-dim);
    padding: 0.5rem 0.25rem;
    transition: color 220ms var(--ease);
  }

  .link-quiet:hover { color: var(--fg); }

  .button {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    min-block-size: var(--target);
    padding-inline: 1.5rem;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    font: inherit;
    font-size: var(--t-2);
    font-weight: 600;
    letter-spacing: 0.005em;
    cursor: pointer;
    /* Magnetic offset, written per-element by the pointer handler. */
    transform: translate3d(calc(var(--mx) * 0.16px), calc(var(--my) * 0.16px), 0);
    transition:
      background-color 260ms var(--ease),
      border-color 260ms var(--ease),
      transform 320ms var(--ease-out);
  }

  .button--sm { min-block-size: 2.375rem; padding-inline: 1.125rem; font-size: var(--t-1); }

  /* One of the two nav-CTA labels, never both. display:none rather than a clip, so exactly one
     reaches the accessibility tree. */
  .on-narrow { display: none; }
  .button--lg { min-block-size: 3.25rem; padding-inline: 2rem; font-size: var(--t-3); }

  .button--primary { background: var(--teal-700); color: #fff; }
  .button--primary:hover { background: var(--teal-600); }

  .button--ghost { border-color: var(--hair-strong); color: var(--fg); }
  .button--ghost:hover { border-color: #fff; background: #ffffff14; }

  .button__arrow {
    inline-size: 0.5rem;
    block-size: 0.5rem;
    border-inline-end: 1.5px solid currentcolor;
    border-block-start: 1.5px solid currentcolor;
    rotate: 45deg;
    transition: translate 300ms var(--ease-out);
  }

  .button:hover .button__arrow { translate: 3px 0; }

  /* Reading position. Bottom edge so it never competes with the nav. */
  .progress {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 90;
    block-size: 2px;
    background: #ffffff14;
  }

  .progress__bar {
    display: block;
    block-size: 100%;
    background: linear-gradient(90deg, var(--teal-700), var(--teal-200));
    transform: scaleX(var(--doc));
    transform-origin: left center;
  }

  .foot {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.75rem 1rem;
    padding: 3.5rem var(--gutter);
    background: var(--ink-abyss);
    border-block-start: 1px solid var(--hair);
    color: var(--fg-faint);
  }

  /* This used to dim the footer wordmark to `--fg-dim`. It no longer can, and should not: the mark's
     three fills are frozen artwork constants and §3.5 forbids recolouring them, so a dimmed logo is
     a recoloured logo. Centred rather than baseline-aligned with the rest of the footer row, because
     the lockup's baseline is its bottom edge and the caption beside it would sit visibly low. */
  .nav__mark--foot { align-self: center; }
  .foot__note { font-size: var(--t-2); }

  .foot__links { margin-inline-start: auto; }

  .foot__links a {
    display: inline-flex;
    align-items: center;
    min-block-size: var(--target);
    font-size: var(--t-2);
    color: var(--fg-dim);
    text-decoration: underline;
    text-decoration-color: #ffffff40;
    text-underline-offset: 0.25em;
    transition: color 220ms var(--ease), text-decoration-color 220ms var(--ease);
  }

  .foot__links a:hover { color: var(--fg); text-decoration-color: currentcolor; }
}

/* ── scenes ───────────────────────────────────────────────────────────────── */

@layer scenes {
  /* Shared interface furniture. Used by the hero and re-used by the pipeline,
     which is what lets one hand over to the other. */
  .panel {
    inline-size: min(30rem, 82vw);
    border: 1px solid var(--hair-strong);
    border-radius: var(--r-lg);
    background: linear-gradient(165deg, #0e2536f2, #081724f2);
    box-shadow: 0 30px 80px -30px #000a, 0 2px 0 0 #ffffff0f inset;
    overflow: hidden;
  }

  .panel--lg { inline-size: min(34rem, 80vw); }

  .panel__bar {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.8125rem 1.125rem;
    border-block-end: 1px solid var(--hair);
    background: #ffffff08;
  }

  .panel__dot {
    inline-size: 0.4375rem;
    block-size: 0.4375rem;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 0 3px #9adfd626;
  }

  .panel__title {
    font-family: var(--mono);
    font-size: var(--t-1);
    letter-spacing: 0.02em;
    color: var(--fg-dim);
  }

  .panel__body { padding: 1.25rem 1.125rem 1.375rem; }

  .metricline {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin-block-end: 1rem;
  }

  .metricline__label { font-size: var(--t-1); color: var(--fg-faint); }

  .metricline__value {
    font-size: var(--t-5);
    font-weight: 700;
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
  }

  .spark { display: flex; align-items: flex-end; gap: 0.3125rem; block-size: 3.25rem; }
  .spark--wide { block-size: 4.5rem; }

  .spark__bar {
    flex: 1;
    border-radius: 2px 2px 0 0;
    background: linear-gradient(180deg, var(--teal-200), #006c7066);
    /* Heights are a deterministic pattern, not random, so the composition is
       stable between renders and between the hero and the pipeline. */
    block-size: calc(28% + (var(--h, 0) * 1%));
    transform-origin: bottom center;
  }

  .spark__bar:nth-child(1) { --h: 18; }
  .spark__bar:nth-child(2) { --h: 42; }
  .spark__bar:nth-child(3) { --h: 30; }
  .spark__bar:nth-child(4) { --h: 58; }
  .spark__bar:nth-child(5) { --h: 47; }
  .spark__bar:nth-child(6) { --h: 66; }
  .spark__bar:nth-child(7) { --h: 38; }
  .spark__bar:nth-child(8) { --h: 52; }
  .spark__bar:nth-child(9) { --h: 61; }
  .spark__bar:nth-child(10) { --h: 44; }
  .spark__bar:nth-child(11) { --h: 70; }
  .spark__bar:nth-child(12) { --h: 55; }

  .lineage { display: flex; flex-wrap: wrap; gap: 0.375rem; margin-block-start: 1.125rem; }

  .lineage__chip {
    padding: 0.25rem 0.5rem;
    border: 1px solid #9adfd633;
    border-radius: var(--r-sm);
    background: #9adfd60f;
    font-family: var(--mono);
    font-size: 0.6875rem;
    color: var(--teal-100);
  }

  /* ── hero ── */

  .hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-content: center;
    min-block-size: 100svh;
    padding: 8rem var(--gutter) 5rem;
    background:
      radial-gradient(70rem 45rem at 78% 8%, #0f4a5e5c 0%, transparent 62%),
      linear-gradient(172deg, var(--ink-abyss) 0%, var(--ink-deep) 55%, #06202c 100%);
    isolation: isolate;
    overflow: clip;
  }

  .hero__field { position: absolute; inset: 0; z-index: -1; }

  /* Background layer, moving slower than the content above it. */
  .hero__grid {
    position: absolute;
    inset: -20% -10% -10%;
    background-image:
      linear-gradient(to right, #ffffff0a 1px, transparent 1px),
      linear-gradient(to bottom, #ffffff0a 1px, transparent 1px);
    background-size: 5.5rem 5.5rem;
    mask-image: radial-gradient(70% 60% at 50% 35%, #000 25%, transparent 78%);
    transform: translate3d(0, calc(var(--p) * 14vh), 0);
  }

  .hero__glow {
    position: absolute;
    inset-block-start: -18%;
    inset-inline-end: -12%;
    inline-size: 60rem;
    block-size: 60rem;
    border-radius: 50%;
    background: radial-gradient(circle, #12758066 0%, transparent 60%);
    filter: blur(20px);
    transform: scale(calc(1 + var(--p) * 0.5));
  }

  .hero__copy { max-inline-size: 46rem; position: relative; z-index: 2; }

  .hero__title { margin-block: 0 1.75rem; }

  /* Line masking. The span is the mask, the inner span is what moves. */
  .line { display: block; overflow: hidden; }
  .line > span { display: block; }
  .line--accent > span { color: var(--accent); }

  .hero__lead {
    max-inline-size: 34rem;
    font-size: var(--t-4);
    color: #c3d7e2;
    line-height: 1.45;
  }

  .hero__actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-block-start: 2.5rem; }

  /* A grid item, not an absolutely positioned overlay. Positioning it against the
     hero's right edge pushed the panel and its floating labels past the viewport,
     where `overflow: clip` cut them off — the exposure figure lost its last
     characters. As a column it cannot overflow, and it stacks under the copy at
     narrow widths for free. */
  .stage {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    justify-self: center;
    perspective: 1400px;
  }

  .stage__depth {
    position: absolute;
    border: 1px solid var(--hair);
    border-radius: var(--r-lg);
    background: #0a1e2c66;
  }

  .stage__depth--back { inline-size: 26rem; block-size: 17rem; rotate: -3deg; translate: -2.5rem -2rem; }
  .stage__depth--mid { inline-size: 27rem; block-size: 18rem; rotate: 2deg; translate: 1.5rem 1rem; }

  .floater {
    position: absolute;
    padding: 0.4375rem 0.75rem;
    border: 1px solid var(--hair-strong);
    border-radius: var(--r-pill);
    background: #071824cc;
    backdrop-filter: blur(8px);
    font-size: var(--t-1);
    color: var(--fg-dim);
    white-space: nowrap;
  }

  .floater--a { inset-block-start: -1.25rem; inset-inline-start: -3.5rem; }
  .floater--b { inset-block-end: -1rem; inset-inline-end: -2rem; }

  .scrollcue {
    position: absolute;
    inset-block-end: 2rem;
    inset-inline-start: var(--gutter);
    display: flex;
    align-items: center;
    gap: 0.625rem;
    font-size: var(--t-1);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--fg-faint);
  }

  .scrollcue span {
    display: block;
    inline-size: 1px;
    block-size: 2.25rem;
    background: linear-gradient(180deg, var(--fg-faint), transparent);
  }

  /* ── pipeline ── */

  .pipeline {
    /* Scroll length. The pinned child is 100vh, so the surplus is the runway the
       four states are spread across. */
    block-size: 340svh;
    background: linear-gradient(180deg, #06202c 0%, var(--ink-deep) 34%, var(--ink-abyss) 100%);
  }

  .pipeline__viewport {
    position: sticky;
    inset-block-start: 0;
    block-size: 100svh;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    gap: clamp(1rem, 3vh, 2.5rem);
    padding: clamp(5rem, 12vh, 7.5rem) var(--gutter) clamp(2rem, 6vh, 4rem);
    overflow: clip;
  }

  /* The whole head leaves as the diagram takes over, eyebrow included. Fading only
     the title left the eyebrow burning at full contrast above a heading that had
     almost gone, which read as a rendering fault rather than a transition. */
  .pipeline__head {
    max-inline-size: 34rem;
    opacity: calc(1 - var(--s3) * 0.88);
    transform: translate3d(0, calc(var(--s3) * -2.5rem), 0);
  }

  .pipeline__title { font-size: var(--t-5); }

  .rig {
    position: relative;
    display: grid;
    place-items: center;
    min-block-size: 0;
    perspective: 1600px;
    transform-style: preserve-3d;
  }

  .rig__core {
    position: relative;
    z-index: 3;
    /* Grows on arrival, then recedes to make room for the diagram, then rushes
       the camera on the way out. One continuous expression of three stages. */
    transform:
      translate3d(0, calc(var(--s4) * -1 * 4vh), calc(var(--s5) * 22rem))
      scale(calc(0.92 + var(--s1) * 0.14 - var(--s3) * 0.2));
    opacity: calc(1 - var(--s5) * 0.9);
  }

  /* Layers that separate out of the core. `--dx/--dy` are the direction, the
     stage supplies the distance, so four layers are four pairs of numbers. */
  .rig__layer {
    position: absolute;
    z-index: 2;
    padding: 0.5rem 0.8125rem;
    border: 1px solid #9adfd63d;
    border-radius: var(--r-sm);
    background: #08202ee6;
    font-family: var(--mono);
    font-size: var(--t-1);
    color: var(--teal-100);
    white-space: nowrap;
    opacity: calc(var(--s2) * (1 - var(--s5)));
    transform: translate3d(
      calc(var(--dx) * var(--s2) * 1rem),
      calc(var(--dy) * var(--s2) * 1rem),
      calc(var(--s2) * -4rem)
    );
  }

  /* Far enough out to clear the core panel. The panel is min(34rem, 80vw), so at
     full width its half is 17rem — the previous 15–17rem offsets left every label
     sitting on top of the thing it was supposed to be separating from. */
  .rig__layer--1 { --dx: -23; --dy: -6; }
  .rig__layer--2 { --dx: 23; --dy: -8; }
  .rig__layer--3 { --dx: -21; --dy: 7; }
  .rig__layer--4 { --dx: 23; --dy: 6; }

  /* The diagram. Steps stagger on `--i`; the shared clamp turns stage progress
     into one sub-progress per child. */
  .flow {
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 4;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(0.5rem, 1.4vw, 1.25rem);
    flex-wrap: nowrap;
  }

  .flow__step {
    --sub: clamp(0, calc((var(--s3) - var(--i) * 0.17) / 0.4), 1);
    min-inline-size: 0;
    max-inline-size: 11rem;
    padding: 0.875rem 1rem;
    border: 1px solid var(--hair-strong);
    border-radius: var(--r-md);
    background: #081c2ae0;
    opacity: calc(var(--sub) * (1 - var(--s5) * 0.9));
    transform: translate3d(0, calc((1 - var(--sub)) * 1.75rem), 0);
  }

  .flow__step[data-step="1"] { --i: 0; }
  .flow__step[data-step="2"] { --i: 1; }
  .flow__step[data-step="3"] { --i: 2; }
  .flow__step[data-step="4"] { --i: 3; }

  .flow__index {
    display: block;
    font-family: var(--mono);
    font-size: 0.6875rem;
    letter-spacing: 0.1em;
    color: var(--accent);
  }

  .flow__name { display: block; font-weight: 600; font-size: var(--t-2); margin-block: 0.25rem 0.1875rem; }
  .flow__note { display: block; font-size: 0.75rem; line-height: 1.45; color: var(--fg-faint); }

  /* A connector is a scaled line. Cheaper than animating a path length and it
     reverses without any special case. */
  .flow__link { inline-size: clamp(1rem, 3.5vw, 3rem); block-size: 1px; }

  .flow__link i {
    display: block;
    block-size: 100%;
    background: linear-gradient(90deg, #9adfd63d, var(--accent));
    transform: scaleX(clamp(0, calc((var(--s3) - var(--i) * 0.17 - 0.09) / 0.34), 1));
    transform-origin: left center;
  }

  /* nth-child, not nth-of-type: every child of .flow is an `li`, so nth-of-type
     would count the steps too and index the wrong elements. The links sit at
     positions 2, 4 and 6 between the four steps. */
  .flow__link:nth-child(2) { --i: 0; }
  .flow__link:nth-child(4) { --i: 1; }
  .flow__link:nth-child(6) { --i: 2; }

  /* ── statement ── */

  .statement {
    display: grid;
    place-items: center;
    min-block-size: 130svh;
    padding: 12vh var(--gutter);
    background: linear-gradient(180deg, var(--ink-abyss) 0%, #061520 100%);
  }

  .statement__text {
    max-inline-size: 52rem;
    font-size: var(--t-6);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.03em;
  }

  .statement__break { display: block; block-size: 0.35em; }

  /* Each word lights as scroll passes it. `--i` is its position in the sentence
     and the multiplier is how many words the scene spends. */
  .w {
    /* Complete by p ~= 0.52, not p ~= 0.81.
       In `cross` mode p = 0.5 is "centred" and p = 1 is "fully exited the top", so a payoff
       keyed near 1 resolves after the reader can no longer see it. Measured on production
       before this: the last word reached full contrast at p = 0.808, with the line at screen
       y = -100 — the sentence the page is built to deliver lit up above the fold line.
       26 and 3: first word starts at p = 0.115, last completes at p = 0.519, and the finished
       sentence then holds, centred, for the back half of the scene. */
    --lit: clamp(0, calc(var(--p) * 26 - 3 - var(--i)), 1);
    display: inline-block;
    color: color-mix(in oklab, #6b8290, var(--fg) calc(var(--lit) * 100%));
    transform: translate3d(0, calc((1 - var(--lit)) * 0.14em), 0);
  }

  .w--accent { color: color-mix(in oklab, #5c9a95, var(--accent) calc(var(--lit) * 100%)); }

  /* nth-child again: `.statement__break` is also a span, so nth-of-type counted
     it and shifted every word after the line break by one. Children are six
     words, the break, then four. */
  .statement__text > *:nth-child(1) { --i: 0; }
  .statement__text > *:nth-child(2) { --i: 1; }
  .statement__text > *:nth-child(3) { --i: 2; }
  .statement__text > *:nth-child(4) { --i: 3; }
  .statement__text > *:nth-child(5) { --i: 4; }
  .statement__text > *:nth-child(6) { --i: 5; }
  .statement__text > *:nth-child(8) { --i: 6.5; }
  .statement__text > *:nth-child(9) { --i: 7.5; }
  .statement__text > *:nth-child(10) { --i: 8.5; }
  .statement__text > *:nth-child(11) { --i: 9.5; }

  /* ── capabilities ── */

  .caps {
    padding-block: clamp(5rem, 12vh, 9rem);
    background: linear-gradient(180deg, #061520 0%, #0a2030 40%, #0d2739 100%);
  }

  .caps__inner {
    display: grid;
    gap: clamp(2rem, 6vw, 5rem);
    max-inline-size: var(--shell);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  .caps__sticky { min-inline-size: 0; }

  .scope { min-inline-size: 0; }

  .scope__frame {
    position: relative;
    aspect-ratio: 4 / 3;
    border: 1px solid var(--hair-strong);
    border-radius: var(--r-lg);
    background: linear-gradient(160deg, #0b2434, #071823);
    box-shadow: 0 30px 70px -30px #000c;
    overflow: hidden;
  }

  .scope__grid {
    position: absolute;
    inset: 0;
    background-image:
      linear-gradient(to right, #ffffff0a 1px, transparent 1px),
      linear-gradient(to bottom, #ffffff0a 1px, transparent 1px);
    background-size: 2.75rem 2.75rem;
    /* Drifts a little as the capability changes, so the frame is not static. */
    transform: translate3d(calc(var(--stage) * -0.5rem), calc(var(--stage) * -0.35rem), 0);
  }

  /* Two data series that reshape between capabilities. `--stage` is continuous,
     so the interface evolves rather than cutting between four pictures. */
  .scope__series {
    position: absolute;
    inset-inline: 8%;
    block-size: 42%;
    border-radius: var(--r-sm);
  }

  .scope__series--a {
    inset-block-end: calc(14% + var(--stage) * 6%);
    background:
      linear-gradient(90deg, #9adfd600 0%, #9adfd63d 12%, #9adfd6 50%, #9adfd63d 88%, #9adfd600 100%);
    clip-path: polygon(
      0% 78%, 12% calc(62% - var(--stage) * 10%), 26% 70%, 38% calc(40% - var(--stage) * 6%),
      52% 56%, 66% calc(30% + var(--stage) * 8%), 80% 48%, 92% calc(36% - var(--stage) * 4%),
      100% 52%, 100% 100%, 0% 100%
    );
    opacity: 0.9;
  }

  .scope__series--b {
    inset-block-end: 10%;
    block-size: 30%;
    background: linear-gradient(90deg, #006c7000, #006c70a6, #006c7000);
    clip-path: polygon(
      0% 86%, 14% calc(70% + var(--stage) * 6%), 30% 78%, 46% calc(54% + var(--stage) * 10%),
      62% 72%, 78% calc(48% + var(--stage) * 6%), 90% 66%, 100% 60%, 100% 100%, 0% 100%
    );
    opacity: calc(0.35 + var(--stage) * 0.2);
  }

  /* A threshold band that slides down the frame as capabilities advance. */
  .scope__band {
    position: absolute;
    inset-inline: 8%;
    inset-block-start: calc(18% + var(--stage) * 13%);
    block-size: 1px;
    background: linear-gradient(90deg, transparent, #9adfd6a6, transparent);
  }

  .scope__cursor {
    position: absolute;
    inset-block: 8% 8%;
    inline-size: 1px;
    inset-inline-start: calc(16% + var(--stage) * 22%);
    background: linear-gradient(180deg, transparent, var(--accent), transparent);
    opacity: 0.7;
  }

  .scope__caption {
    margin-block-start: 1rem;
    font-family: var(--mono);
    font-size: var(--t-1);
    letter-spacing: 0.02em;
    color: var(--fg-faint);
  }

  .caps__list { display: grid; gap: clamp(4rem, 14vh, 9rem); min-inline-size: 0; }

  .cap {
    --sub: var(--s1);
    min-inline-size: 0;
    max-inline-size: 34rem;
    /* 0.85, not 0.62. Measured on production in the section's resting state: the
       supporting copy came out at 3.64:1 and the detail line at 3.28:1 against the
       lightest stop of this section's gradient, both under the 4.5:1 small text needs.
       0.85 puts them at 5.58 and 4.91. The earlier 0.62 was checked while the section
       happened to be lit, which is the state that cannot fail. */
    opacity: calc(0.85 + var(--sub) * 0.15);
    transform: translate3d(0, calc((1 - var(--sub)) * 1.5rem), 0);
    transition: opacity 120ms linear;
  }

  .cap[data-cap="2"] { --sub: var(--s2); }
  .cap[data-cap="3"] { --sub: var(--s3); }
  .cap[data-cap="4"] { --sub: var(--s4); }

  .cap__index {
    font-family: var(--mono);
    font-size: var(--t-1);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent);
    margin-block-end: 0.875rem;
  }

  .cap h3 { margin-block-end: 0.875rem; }
  .cap__body { color: var(--fg-dim); }

  .cap__detail {
    margin-block-start: 1rem;
    padding-inline-start: 0.875rem;
    border-inline-start: 2px solid #9adfd63d;
    font-size: var(--t-2);
    color: var(--fg-faint);
  }

  /* ── visual break ── */

  .break {
    display: grid;
    place-items: center;
    min-block-size: 120svh;
    padding: 14vh var(--gutter);
    background: linear-gradient(180deg, #0d2739 0%, #061622 45%, var(--ink-abyss) 100%);
    overflow: clip;
  }

  /* Sized so the longest row still fits at its largest scale. Reusing --t-7 here
     let the type grow past the container mid-scene, and a centre-out clip on text
     that is already overflowing reads as a cropping bug rather than a reveal. */
  .break__type {
    inline-size: 100%;
    max-inline-size: var(--shell);
    font-size: clamp(1.875rem, 7.6vw, 5.5rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.045em;
    text-align: center;
  }

  /* Rows scale down toward their resting size and unclip as they arrive, with a
     slight opposed drift so the block reads as depth rather than a zoom. */
  .break__row {
    display: block;
    overflow: hidden;
    /* `--open` reaches 1 at p = 0.455 and holds. Keyed to p directly, the clip only finished
       opening as the row left the viewport: measured at p = 0.5 it still removed 25% from each
       side (letters amputated mid-word), and at p = 0.99, when it was finally open, the row sat
       at screen y = -652. The headline was never once whole and on screen. Shrinking the type,
       which is what the previous fix did, treated the symptom. */
    --open: clamp(0, calc(var(--p) * 2.2), 1);
    clip-path: inset(0 calc((1 - var(--open)) * 50%) 0 calc((1 - var(--open)) * 50%));
  }

  .break__row > span {
    display: block;
    transform:
      translate3d(calc(var(--dir, 0) * (1 - var(--open)) * 2.5vw), 0, 0)
      scale(calc(1.06 - var(--open) * 0.06));
  }

  .break__row:nth-child(1) > span { --dir: -1; }
  .break__row:nth-child(2) > span { --dir: 1; }
  .break__row:nth-child(3) > span { --dir: -1; }
  .break__row--accent > span { color: var(--accent); }

  /* ── horizontal rail ── */

  .rail {
    block-size: 320svh;
    background: linear-gradient(180deg, var(--ink-abyss) 0%, #071c28 100%);
  }

  .rail__viewport {
    position: sticky;
    inset-block-start: 0;
    block-size: 100svh;
    display: grid;
    /* The column has to be constrained explicitly. An implicit `auto` column is
       sized to its widest item's max-content, and the track's max-content is the
       full width of five cards — so the column grew to 2000px, the track was never
       narrower than its own contents, and there was no overflow left to translate.
       The horizontal sequence simply did not move. */
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    align-content: center;
    gap: clamp(1.5rem, 5vh, 3rem);
    padding-block: clamp(5rem, 12vh, 8rem) clamp(3rem, 8vh, 5rem);
    overflow: clip;
  }

  .rail__head { max-inline-size: 40rem; padding-inline: var(--gutter); }
  .rail__head h2 { font-size: var(--t-4); color: var(--fg-dim); font-weight: 400; }

  .rail__track {
    display: flex;
    align-items: stretch;
    gap: clamp(1rem, 2vw, 1.75rem);
    padding-inline: var(--gutter);
    /* --shift is the overflow width in px, measured once per resize. */
    transform: translate3d(calc(var(--p) * var(--shift, 0px) * -1), 0, 0);
    will-change: transform;
  }

  .railcard {
    flex: 0 0 auto;
    inline-size: min(22rem, 74vw);
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    padding: clamp(1.5rem, 3vw, 2.25rem);
    border: 1px solid var(--hair);
    border-radius: var(--r-lg);
    background: linear-gradient(165deg, #0c2333, #08192480);
    transition: border-color 300ms var(--ease), transform 400ms var(--ease-out);
  }

  .railcard:hover { border-color: #9adfd64d; transform: translateY(-4px); }
  .railcard__index { font-family: var(--mono); font-size: var(--t-1); color: var(--accent); letter-spacing: 0.1em; }
  .railcard h3 { font-size: var(--t-4); }
  .railcard p { font-size: var(--t-2); color: var(--fg-dim); }

  /* ── figures ── */

  .figures {
    padding: clamp(5rem, 14vh, 10rem) var(--gutter);
    background: linear-gradient(180deg, #071c28 0%, #0a2434 100%);
  }

  .figures__head { max-inline-size: 44rem; margin-inline: auto; text-align: center; }
  .figures__head h2 { margin-block-end: 1.25rem; }
  .figures__note { font-size: var(--t-2); color: var(--fg-faint); max-inline-size: 34rem; margin-inline: auto; }

  .figures__list {
    display: grid;
    gap: clamp(2rem, 4vw, 3rem);
    max-inline-size: var(--shell);
    margin: clamp(3rem, 8vh, 5rem) auto 0;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  }

  .figure {
    --sub: var(--s1);
    min-inline-size: 0;
    /* 0.85 floor, not 0.64: the supporting note under each figure measured 3.48:1
       once the fade was composited against the lighter figures background. The reveal
       here is carried by the counter and the expanding rule, so the fade can be slight. */
    /* No translate. The stagger left three 112px numerals on baselines 18px apart — measured
       187 / 187 / 205 — which reads as a rendering fault rather than choreography. The reveal
       is carried by the opacity and by the rule expanding underneath. */
    opacity: calc(0.85 + var(--sub) * 0.15);
  }

  .figure[data-figure="2"] { --sub: var(--s2); }
  .figure[data-figure="3"] { --sub: var(--s3); }

  .figure__value {
    font-size: var(--t-7);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.05em;
    font-variant-numeric: tabular-nums;
    color: var(--fg);
  }

  .figure__label { margin-block: 1rem 0.5rem; font-weight: 600; font-size: var(--t-3); }
  .figure__note { font-size: var(--t-2); color: var(--fg-faint); }

  .figure__rule {
    display: block;
    block-size: 2px;
    margin-block-start: 1.5rem;
    background: linear-gradient(90deg, var(--accent), #006c7000);
    transform: scaleX(var(--sub));
    transform-origin: left center;
  }

  /* ── before → after ── */

  .shift {
    block-size: 300svh;
    background: linear-gradient(180deg, #0a2434 0%, #050d14 45%, #03080d 100%);
  }

  .shift__viewport {
    position: sticky;
    inset-block-start: 0;
    block-size: 100svh;
    display: grid;
    align-content: center;
    gap: clamp(1.5rem, 5vh, 3rem);
    padding: clamp(5rem, 12vh, 7rem) var(--gutter);
    overflow: clip;
  }

  .shift__labels {
    position: relative;
    display: flex;
    justify-content: space-between;
    max-inline-size: 46rem;
    inline-size: 100%;
    margin-inline: auto;
    font-family: var(--mono);
    font-size: var(--t-1);
    letter-spacing: 0.16em;
    text-transform: uppercase;
  }

  /* The two headings trade prominence rather than swapping visibility. */
  .shift__tag--before {
    color: color-mix(in oklab, var(--fg), #7a919e calc(var(--p) * 100%));
    transform: translate3d(0, calc(var(--p) * -0.5rem), 0);
  }

  .shift__tag--after {
    color: color-mix(in oklab, #5c9a95, var(--accent) calc(var(--p) * 100%));
    transform: translate3d(0, calc((1 - var(--p)) * 0.5rem), 0);
  }

  .shift__rows {
    display: grid;
    gap: clamp(0.75rem, 2.5vh, 1.5rem);
    max-inline-size: 46rem;
    inline-size: 100%;
    margin-inline: auto;
  }

  /* One row, two labels, one connecting line. The row does not crossfade between
     two states: the left label recedes, the line draws across, the right label
     arrives, and the same grid holds all three. */
  .shiftrow {
    --sub: clamp(0, calc((var(--stage) - var(--i)) / 1.15), 1);
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: clamp(0.75rem, 3vw, 2rem);
    padding-block: clamp(0.5rem, 1.6vh, 1rem);
    border-block-end: 1px solid var(--hair);
    font-size: var(--t-4);
    font-weight: 600;
    letter-spacing: -0.02em;
  }

  .shiftrow[data-row="1"] { --i: 0; }
  .shiftrow[data-row="2"] { --i: 0.55; }
  .shiftrow[data-row="3"] { --i: 1.1; }
  .shiftrow[data-row="4"] { --i: 1.65; }

  /* Grid items default to a min-content floor, so at 320px the "after" column
     refused to shrink and its last few pixels sat outside the viewport. */
  .shiftrow__before,
  .shiftrow__after { min-inline-size: 0; }

  .shiftrow__before {
    color: color-mix(in oklab, var(--fg), #7a919e calc(var(--sub) * 100%));
    transform: translate3d(calc(var(--sub) * -1.25rem), 0, 0);
  }

  .shiftrow__line {
    inline-size: clamp(2rem, 10vw, 7rem);
    block-size: 1px;
    background: linear-gradient(90deg, #ffffff26, var(--accent));
    transform: scaleX(var(--sub));
    transform-origin: left center;
  }

  .shiftrow__after {
    text-align: right;
    color: color-mix(in oklab, #5c9a95, var(--accent) calc(var(--sub) * 100%));
    transform: translate3d(calc((1 - var(--sub)) * 1.5rem), 0, 0);
  }

  /* ── boundaries ── */

  .bounds {
    padding: clamp(5rem, 13vh, 9rem) var(--gutter);
    background: #03080d;
  }

  .bounds .measure { margin-inline: auto; text-align: center; }
  .bounds h2 { margin-block-end: 1rem; }

  .bounds__list {
    display: grid;
    gap: 2rem clamp(2rem, 5vw, 4rem);
    max-inline-size: var(--shell);
    margin: clamp(3rem, 7vh, 4.5rem) auto 0;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  }

  .bounds__list li {
    --sub: var(--s1);
    min-inline-size: 0;
    padding-inline-start: 1.125rem;
    border-inline-start: 2px solid #ffffff1a;
    opacity: calc(0.66 + var(--sub) * 0.34);
    transform: translate3d(0, calc((1 - var(--sub)) * 1rem), 0);
  }

  .bounds__list h3 { font-size: var(--t-3); margin-block-end: 0.5rem; }
  .bounds__list p { font-size: var(--t-2); color: var(--fg-dim); }

  /* ── close ── */

  .close {
    position: relative;
    display: grid;
    place-items: center;
    min-block-size: 100svh;
    padding: 12vh var(--gutter);
    background: linear-gradient(180deg, #03080d 0%, #052026 55%, #04333a 100%);
    isolation: isolate;
    overflow: clip;
  }

  .close__aura {
    position: absolute;
    inset-block-end: -45%;
    inline-size: 70rem;
    block-size: 70rem;
    border-radius: 50%;
    background: radial-gradient(circle, #0c7a7f4d 0%, transparent 62%);
    filter: blur(10px);
    z-index: -1;
    transform: scale(calc(0.6 + var(--p) * 0.55));
    opacity: calc(0.3 + var(--p) * 0.7);
  }

  /* Converges toward centre as the section arrives, then holds. */
  .close__inner {
    max-inline-size: 44rem;
    text-align: center;
    transform: translate3d(0, calc((1 - var(--s1)) * 3.5rem), 0) scale(calc(0.965 + var(--s1) * 0.035));
  }

  .close__title { font-size: var(--t-6); }
  .close__actions { display: flex; flex-wrap: wrap; gap: 0.875rem; justify-content: center; margin-block-start: 2.5rem; }
}

/* ── motion ───────────────────────────────────────────────────────────────
 *
 * The entrance sequence, and the two guards.
 *
 * Everything that hides content is scoped to `.js`, which landing.js adds to the
 * root element. A blocked, failed or disabled script therefore leaves the whole
 * page readable rather than blank — the failure mode of a scroll-reveal site is
 * that it shows nothing at all, and it is invisible until it happens to someone
 * else.
 */

@layer motion {
  /* Load-time entrance. Ordered by `data-enter`, not by source position, so the
     sequence is editable in the markup. */
  /* Everything except a masked line moves as itself. */
  .js [data-enter]:not(.line) {
    opacity: 0;
    transform: translate3d(0, 1.5rem, 0);
    transition:
      opacity 900ms var(--ease-out),
      transform 1100ms var(--ease-out);
    transition-delay: var(--enter-delay, 0ms);
  }

  .js.is-ready [data-enter]:not(.line) {
    opacity: 1;
    transform: none;
  }

  /* A masked line is the other way round: the `.line` is the aperture and stays
     put, and the span inside it rises through. Animating the `.line` itself would
     move the mask along with its contents and reveal nothing.
     `--enter-delay` is set on the `.line` and inherits to the span. */
  .js .line[data-enter] > span {
    transform: translate3d(0, 105%, 0);
    transition: transform 1100ms var(--ease-out);
    transition-delay: var(--enter-delay, 0ms);
  }

  .js.is-ready .line[data-enter] > span { transform: none; }

  /* Nav arrives from above, slightly after the first copy. */
  .js .nav { opacity: 0; transform: translate3d(0, -100%, 0); transition: opacity 700ms var(--ease-out), transform 800ms var(--ease-out), padding 420ms var(--ease), background-color 420ms var(--ease), border-color 420ms var(--ease); transition-delay: 240ms; }
  .js.is-ready .nav { opacity: 1; transform: none; }

  /* Hero content leaves as the pipeline takes over. Scroll-linked, so it comes
     back on the way up. */
  .js .hero__copy {
    transform: translate3d(0, calc(var(--p) * -16vh), 0);
    opacity: clamp(0, calc(1 - var(--p) * 1.9), 1);
  }

  .js .scrollcue { opacity: clamp(0, calc(1 - var(--p) * 4), 1); }

  /* The handoff: the hero visual grows toward the viewer and becomes the object
     the next section is about. */
  .js .stage {
    transform:
      translate3d(calc(var(--p) * -5vw), calc(var(--p) * 5vh), 0)
      scale(calc(1 + var(--p) * 0.38))
      rotateX(calc(var(--ty) * -0.6deg))
      rotateY(calc(var(--tx) * 0.9deg));
    opacity: clamp(0, calc(1 - var(--p) * 1.15), 1);
  }

  .js .rig {
    transform: rotateX(calc(var(--ty) * -0.5deg)) rotateY(calc(var(--tx) * 0.7deg));
  }

  .js .scope {
    transform: rotateX(calc(var(--ty) * -0.4deg)) rotateY(calc(var(--tx) * 0.6deg));
    transition: transform 500ms var(--ease-out);
  }

  /* Cards illuminate along the pointer rather than glowing constantly. */
  .railcard {
    background-image:
      radial-gradient(
        14rem 14rem at var(--cx, 50%) var(--cy, 0%),
        #9adfd612 0%, transparent 70%
      ),
      linear-gradient(165deg, #0c2333, #08192480);
  }

  @media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

    /* Content first: everything visible, nothing displaced, no scroll-linked
       transform anywhere. Information is preserved; only the motion goes. */
    .js [data-enter],
    .js .hero__copy,
    .js .stage,
    .js .cap,
    .js .figure,
    .js .bounds__list li,
    .js .flow__step,
    .js .rig__core,
    .js .rig__layer,
    .js .w,
    .js .break__row > span,
    .js .shiftrow__before,
    .js .shiftrow__after,
    .js .close__inner,
    .js .scrollcue,
    .js .pipeline__head {
      opacity: 1 !important;
      transform: none !important;
      transition: none !important;
      animation: none !important;
    }

    /* Removing the motion is not enough: the scroll length has to go too.
       The pinned scenes buy their runway with height — 340svh, 320svh, 300svh — and that height
       exists only to give the animation somewhere to happen. With the animation gone it is 18
       screens of static, largely empty page, which is a worse experience than the animated one
       rather than a safer one. Collapsing the three runways takes the document from ~18 screens
       to ~10. */
    .js .pipeline,
    .js .rail,
    .js .shift { block-size: auto; }

    .js .pipeline__viewport,
    .js .rail__viewport,
    .js .shift__viewport {
      position: static;
      block-size: auto;
      overflow: visible;
      padding-block: clamp(4rem, 10vh, 6rem);
    }

    /* And a content defect inside the branch that must not lose content. `transform: none` parks
       all four labels at the grid centre, behind the panel — "lead-time variance" rendered as grey
       ghosting through the panel body. In flow they cannot collide. */
    .js .rig {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 1.25rem;
      perspective: none;
    }

    .js .rig__layer { position: static; }
    .js .flow { position: static; flex-wrap: wrap; justify-content: center; }
    .js .rail__track { flex-direction: column; }
    .js .railcard { inline-size: auto; }

    .js .break__row { clip-path: none !important; }
    .js .rail__track { transform: none !important; }
    .js .flow__link i,
    .js .shiftrow__line,
    .js .figure__rule { transform: scaleX(1) !important; }
    .js .nav { opacity: 1 !important; transform: none !important; }

    .w, .w--accent { color: var(--fg) !important; }
    .shiftrow__before, .shiftrow__after { color: var(--fg) !important; }
    .shift__tag--before, .shift__tag--after { color: var(--fg) !important; }

    .button, .railcard, .nav__links a::after { transition: none !important; }
  }
}

/* ── adapt ────────────────────────────────────────────────────────────────
 *
 * Not a shrunk desktop. Each breakpoint removes the effects that cost the most
 * and keeps the narrative: the pinned scenes stop pinning, the horizontal rail
 * becomes an ordinary vertical list, and the multi-layer scenes lose their
 * layers rather than rendering them at a size nobody can read.
 */

@layer adapt {
  @media (min-width: 60rem) {
    .hero { grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); align-items: center; }
    .hero__copy { grid-column: 1; }
    .caps__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
    .caps__sticky { position: sticky; inset-block-start: 7.5rem; }
    .caps__list { padding-block: 12vh 20vh; }
  }

  /* Tablet: reduced complexity. The hero visual stops competing for width and
     the deep layers go. */
  @media (max-width: 59.99rem) {
    .stage { margin-block-start: 4rem; justify-self: start; }
    .js .stage { transform: scale(calc(1 + var(--p) * 0.12)); opacity: clamp(0, calc(1 - var(--p) * 1.4), 1); }
    .stage__depth { display: none; }
    .floater--a { inset-block-start: -1rem; inset-inline-start: auto; inset-inline-end: 0; }
    .floater--b { display: none; }
    .pipeline { block-size: 300svh; }
    .rig__layer--3, .rig__layer--4 { display: none; }
    .flow { position: static; flex-wrap: wrap; justify-content: flex-start; }
    .flow__link { display: none; }
    .flow__step { max-inline-size: none; flex: 1 1 8rem; }
  }

  /* The quiet "Sign in", dropped below 54rem. It and the "Get started" button beside it point at the
     same `/app`, and the hero repeats the same pair a screen-height below, so this is the third copy
     of one destination and the cheapest thing in the bar to lose.

     It is dropped here rather than only on mobile because of an arithmetic collision the real lockup
     exposed. The desktop bar needs 139 (lockup) + 381 (section links) + 195 (both actions) + 32
     (two gaps) = 747px, and `--gutter` is 4.5vw in this range, so it needs a 821px viewport — while
     the desktop bar itself starts at 768px. Between those two numbers "Sign in" wrapped to a second
     line and the bar grew from 89px to 109px. [measured at 768px] Without the quiet link the bar
     needs 704px and fits everywhere it is used. The 14px gradient square this replaced happened to
     hide the collision by being 68px narrower than the artwork it stood in for. */
  @media (max-width: 53.99rem) {
    .nav .link-quiet { display: none; }
  }

  /* Mobile: the horizontal rail becomes vertical, the pinned scenes unpin. A
     pinned 100vh scene on a short viewport with a dynamic toolbar is a fight
     nobody wins. */
  @media (max-width: 47.99rem) {
    .nav__links { display: none; }
    .nav__actions { margin-inline-start: auto; }

    /* The lockup stays at 139px here, and the button gives way instead.
       139px is not a rounded-up 120: it is the width at which the mark inside the
       artwork — 77 of the lockup's 443.87 units — measures 24.11px and so clears
       the mark's own floor as well as the lockup's. Dropping to the 120px lockup
       floor would put the mark at 20.8px, under its floor, which is a brand
       decision rather than a layout one and not one to make from a media query.

       So the space comes out of the button: no arrow, tighter padding, no wrap.
       At 320px that leaves the 24px of clear space §3.5 asks for around the mark.
       If the primary action ever needs a longer label than this, the answer is the
       mark alone (§3.5, "Below that, use the mark"), not a smaller lockup. */
    .nav .button--sm { white-space: nowrap; padding-inline: 0.75rem; }
    .nav .button--sm .button__arrow { display: none; }
    .on-wide { display: none; }
    .on-narrow { display: inline; }

    .rail { block-size: auto; }
    .rail__viewport { position: static; block-size: auto; overflow: visible; }
    .rail__track {
      flex-direction: column;
      transform: none !important;
      gap: 1rem;
    }
    .railcard { inline-size: auto; }

    .shift { block-size: auto; }
    .shift__viewport { position: static; block-size: auto; padding-block: clamp(4rem, 10vh, 6rem); }
    .shiftrow { font-size: var(--t-2); gap: 0.5rem; }
    .shiftrow__line { inline-size: 1rem; }
    /* The horizontal slide is 1.5rem, which at 320px pushes the label past the
       clip edge and cuts the last characters off mid-animation. Colour and the
       connector still carry the transformation; the travel is what goes. */
    .js .shiftrow__before,
    .js .shiftrow__after { transform: none; }

    .pipeline { block-size: auto; }
    .pipeline__viewport { position: static; block-size: auto; overflow: visible; padding-block: clamp(4rem, 10vh, 6rem); }
    .rig { gap: 2rem; }
    .rig__layer { display: none; }
    .js .rig__core { transform: none; opacity: 1; }
    .js .pipeline__head { opacity: 1; transform: none; }

    /* At 320px it lands on top of the panel's own title row. */
    .floater--a { display: none; }

    .statement { min-block-size: auto; padding-block: 18vh; }
    .break { min-block-size: auto; padding-block: 16vh; }
    .close { min-block-size: auto; padding-block: 16vh; }
  }

  /* Pointer-driven effects are desktop-only. On a touch device `--tx/--ty` never
     change, so the tilt would be dead weight in every style recalculation. */
  @media (hover: none), (pointer: coarse) {
    .js .stage,
    .js .rig,
    .js .scope {
      rotate: none;
    }

    .button { transform: none; }
  }

  @media (forced-colors: active) {
    .panel, .flow__step, .railcard, .scope__frame, .bounds__list li { border-color: CanvasText; }
    .button { border-color: ButtonText; }
    .w, .w--accent, .shiftrow__before, .shiftrow__after { color: CanvasText !important; }

    /* Forced colours do not repaint SVG fills, and the lockup in the nav and footer is the reversed
       one: its wordmark is `#F6F6F6`, which disappears the moment a forced palette makes `Canvas`
       light. A forced palette is the single-ink case, and BRAND.md §3's sanctioned answer there is
       the one-colour lockup — so the whole lockup, mark included, follows `CanvasText`.
       [verified in Chrome 151: contrast measured, and the propagation below confirmed by probe]

       The selector is `.sprite path` and not `.nav__lockup path` deliberately, and this is the part
       that is easy to get wrong: the paths a `<use>` renders live in a shadow tree that a light-DOM
       selector cannot reach, so `.nav__lockup path` matches nothing at all. Restyling the `<symbol>`
       source paths — which are ordinary light-DOM children of `.sprite` — does propagate into every
       instance. No `!important`: a `fill` presentation attribute loses to any author rule. */
    .sprite path { fill: CanvasText; }
  }
}

/* ---------------------------------------------------------------------------
   The published legal documents at /legal.
   ---------------------------------------------------------------------------
   The same shell as the landing page — same stylesheet, skip link, wordmark and
   footer — because they are the same site and a buyer moving between them should
   not feel handed off. What differs is that this is a document rather than a
   pitch: one column, generous measure, and tables that survive 320px.

   Everything here is additive and scoped under `.legal`, so nothing the landing
   page renders can change. */
@layer components {
  body.legal {
    /* The legal documents own their palette, type scale and metrics, rather than reading the
       landing page's. They used to read `:root`, and when the landing page was rebuilt as a dark
       scroll-driven narrative every token these rules name went with it: the block was deleted, and
       `body.legal` was dropped from the base layer's selector list. The published privacy policy,
       DPA and sub-processor list rendered as unstyled Times on the near-black canvas, and the
       four-column AWS table took the page sideways at 320px again. Declaring them here means a
       future re-theme of the landing page cannot reach these documents.

       `color-scheme: light` is part of that: `:root` now declares `dark`, which is what made the
       text white and kept the pages accidentally legible over the dark canvas. These are light
       documents and say so. */
    color-scheme: light;
    --white: #fff;
    --ink: var(--navy-900);
    --border: #c5d1d8;
    --slate-50: #f6f9fa;
    --slate-100: #edf2f4;
    --radius-md: 0.625rem;
    --focus: #026aa2;
    --gutter: clamp(1.25rem, 5vw, 3.5rem);
    --measure: 44rem;
    --step--1: 0.875rem;
    --step-0: 1rem;
    --step-1: clamp(1.0625rem, 0.99rem + 0.35vw, 1.25rem);
    --step-3: clamp(1.75rem, 1.4rem + 1.7vw, 2.75rem);
    margin: 0;
    font-family: var(--font);
    font-size: var(--step-0);
    line-height: 1.6;
    color: var(--ink);
    display: grid;
    grid-template-rows: auto 1fr auto;
    /* `minmax(0, 1fr)`, not the implicit `auto`. A grid column sized `auto` takes the min-content width
       of its widest item, and this page's widest item is a four-column table of AWS services. At a
       320px viewport that made the column 456px and took the whole document sideways — 497px of
       scrollWidth against a 320px client width, which is a WCAG 2.2 SC 1.4.10 reflow failure. The
       `minmax(0, ...)` floor lets the column be narrower than its content so the table's own
       `overflow-x: auto` can do its job. The same trap is documented on `.measure` above; it caught
       this layout too, one level up. */
    grid-template-columns: minmax(0, 1fr);
    min-block-size: 100vh;
    background: var(--white);
  }

  .legal-header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--gutter);
    border-block-end: 1px solid var(--border);
    padding: var(--gutter);
  }

  /* The footer's first link. At its natural line box it is 19px tall, under the 24x24 minimum in WCAG
     2.2 SC 2.5.8 — and the "inline in a sentence" exception does not apply, because it sits alone in a
     nav rather than inside running text. The audit did not catch this: checkTargetSize runs over `main`
     and the footer is outside it. */
  .site-footer nav a {
    display: inline-block;
    padding-block: 0.25rem;
  }

  .legal-header .wordmark {
    /* `.wordmark` is white, because until now it only ever sat on the navy topbar and footer. On this
       page's light header that renders white on white — invisible, and 1:1 against the background. The
       footer wordmark below is untouched and stays white, which is correct there. */
    color: var(--ink);
    text-decoration: none;
  }

  .legal-doc {
    /* The measure is the reason this reads as a document. A legal page at full
       shell width is the kind nobody finishes. */
    margin-inline: auto;
    /* `inline-size: 100%` is load-bearing and not a tidy-up. `margin-inline: auto` on a grid item
       switches its used width from "stretch to the grid area" to shrink-to-fit, and shrink-to-fit
       around a four-column table resolves to the table's min-content — 498px inside a 320px column,
       which took the document to 497px of scrollWidth and failed SC 1.4.10. An explicit width means
       the box fills the area, `max-inline-size` still caps it on a wide screen, and the auto margins
       still centre it once it is capped. */
    inline-size: 100%;
    padding: clamp(1.5rem, 5vw, 4rem) var(--gutter);
  }

  .legal-doc > * + * {
    margin-block-start: 1em;
  }

  .legal-doc h1 {
    font-size: var(--step-3);
    line-height: 1.1;
    letter-spacing: -0.02em;
  }

  .legal-doc h2 {
    margin-block-start: 2.25em;
    font-size: var(--step-1);
    letter-spacing: -0.01em;
  }

  .legal-doc h3 {
    margin-block-start: 1.75em;
    font-size: var(--step-0);
  }

  .legal-doc p {
    /* An identifier written into prose — `us-east-1`, a table of AWS services —
       has a min-content width equal to its own length and will hold the column
       open past the viewport. `anywhere` reduces that width; `break-word` does
       not, which is the difference that matters at 320px. */
    overflow-wrap: anywhere;
  }

  .legal-doc code {
    border-radius: 0.25rem;
    background: var(--slate-100);
    padding: 0.1em 0.35em;
    font-size: 0.9em;
  }

  .legal-doc hr {
    border: 0;
    border-block-start: 1px solid var(--border);
    margin-block: 2.5em;
  }

  /* A table wider than the viewport scrolls inside its own region rather than
     scrolling the document sideways. It is focusable and labelled so the scroll
     is reachable from the keyboard, which is what makes the overflow acceptable
     rather than a 1.4.10 reflow failure. */
  .legal-table {
    overflow-x: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
  }

  .legal-table:focus-visible {
    outline: 3px solid var(--focus);
    outline-offset: 2px;
  }

  .legal-doc table {
    border-collapse: collapse;
    inline-size: 100%;
    font-size: var(--step--1);
  }

  .legal-doc th,
  .legal-doc td {
    border-block-end: 1px solid var(--border);
    padding: 0.75rem;
    text-align: start;
    vertical-align: top;
  }

  .legal-doc thead th {
    background: var(--slate-50);
    white-space: nowrap;
  }

  .legal-doc tbody tr:last-child th,
  .legal-doc tbody tr:last-child td {
    border-block-end: 0;
  }

  .legal-doc tbody th {
    font-weight: 700;
    min-inline-size: 8rem;
  }
}
