/* ==========================================================================
   EXISTENCE BEYOND TESTIMONY — shared stylesheet
   Linked by index.html (landing) and buy.html (product page, served at /buy).

   This was index.html's inline <style>, moved out unchanged so both pages
   share ONE set of design tokens, ONE .cta component and ONE footer rather
   than a copy each. Page-specific rules stay in that page's own <style>.
   ========================================================================== */

    /* ======================================================================
       01 — TYPEFACE
       ====================================================================== */
    @font-face {
      font-family: "HS LunaObscura";
      src: url("fonts/HS_LunaObscura.woff2") format("woff2");
      font-weight: 400 900;
      font-style: normal;
      font-display: swap;
    }

    /* ======================================================================
       02 — DESIGN TOKENS
       ====================================================================== */
    :root {
      --void:       #000000;

      /* The brand blue. Used for fills, glows and the background field.
         At 1.44:1 on black it cannot carry text, so --blue-lift is the same
         hue raised to a legible lightness for type and hairlines. */
      --blue:       #001c78;
      --blue-deep:  #000c33;
      --blue-lift:  #3D6AFF;
      --blue-dim:   rgba(61, 106, 255, .34);
      --ice-blue:   #38BDF8;   /* system tags */
      /* Silver, matching the cards' own line art. A blue label on the navy
         stock only reaches ~4.1:1; this reads at 8:1 and belongs to the
         artwork rather than fighting it. */
      --card-ink: #C9D6EC;

      --white:      #FFFFFF;
      --ice:        #E2E8F0;
      --ice-mute:   rgba(226, 232, 240, .58);

      --hair:       rgba(61, 106, 255, .26);

      --display: "HS LunaObscura", "Arial Narrow", Impact, sans-serif;
      --body:    "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
      --data:    "Space Mono", ui-monospace, Consolas, monospace;

      --gutter: clamp(20px, 4.2vw, 72px);
      --ease:   cubic-bezier(.22, 1, .36, 1);

      /* Card artwork is 760x1150, so height = width x 1.5131 exactly. */
      --card-w: clamp(250px, 22vw, 360px);
      --card-h: calc(var(--card-w) * 1.5131);

      --scroll-progress: 0;
      --void-mix: 0;
    }

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

    html {
      background: var(--void);
      color: var(--ice);
      font-family: var(--body);
      -webkit-font-smoothing: antialiased;
    }

    body {
      margin: 0;
      min-width: 320px;
      background: var(--void);
      color: var(--ice);
      overflow-x: clip;
    }

    body.is-loading { overflow: hidden; }

    a { color: inherit; }
    button, a { -webkit-tap-highlight-color: transparent; }
    :focus-visible { outline: 2px solid var(--blue-lift); outline-offset: 4px; }

    .sr-only {
      position: absolute;
      width: 1px; height: 1px;
      padding: 0; margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }

    /* ======================================================================
       03 — VOID FIELD (scroll-driven background interpolation)
       ====================================================================== */
    .void-field {
      position: fixed;
      inset: 0;
      z-index: -1;
      background: var(--void);
      pointer-events: none;
    }

    .void-field__deep {
      position: absolute;
      inset: 0;
      background:
        radial-gradient(120% 80% at 50% 108%, rgba(0, 28, 120, .70), transparent 64%),
        linear-gradient(180deg, var(--blue-deep) 0%, var(--blue) 100%);
      opacity: var(--void-mix);
    }

    /* ======================================================================
       04 — LOADER
       ====================================================================== */
    .loader {
      position: fixed;
      inset: 0;
      z-index: 1000;
      display: grid;
      grid-template-rows: 1fr auto;
      padding: max(24px, env(safe-area-inset-top)) var(--gutter) max(24px, env(safe-area-inset-bottom));
      background: var(--void);
      transition: clip-path 900ms var(--ease), visibility 900ms;
      clip-path: inset(0 0 0 0);
    }

    .loader.is-done { clip-path: inset(0 0 100% 0); visibility: hidden; }

    .loader__mark { align-self: center; max-width: 44rem; }

    .loader__glyphs {
      margin: 0 0 22px;
      font-family: var(--data);
      font-size: clamp(12px, 2.2vw, 18px);
      line-height: 1.7;
      letter-spacing: .18em;
      color: var(--blue-lift);
      opacity: .42;
      word-break: break-all;
    }

    .loader__title {
      margin: 0;
      font-family: var(--display);
      font-size: clamp(30px, 6vw, 74px);
      font-weight: 700;
      line-height: .9;
      letter-spacing: .02em;
      text-transform: uppercase;
      color: var(--white);
    }

    .micro {
      font-family: var(--data);
      font-size: 10px;
      line-height: 1.6;
      letter-spacing: .14em;
      text-transform: uppercase;
    }

    .loader__foot {
      display: grid;
      grid-template-columns: auto 1fr auto;
      align-items: center;
      gap: 18px;
      padding-top: 22px;
      border-top: 1px solid var(--hair);
      color: var(--blue-lift);
    }

    .loader__track,
    .rail__track {
      position: relative;
      height: 1px;
      overflow: hidden;
      background: rgba(61, 106, 255, .24);
    }

    .loader__fill,
    .rail__fill {
      position: absolute;
      inset: 0;
      background: var(--blue-lift);
      box-shadow: 0 0 12px var(--blue-lift);
      transform: scaleX(0);
      transform-origin: left;
    }

    /* ======================================================================
       05 — FILM STAGE
       ====================================================================== */
    /* Scroll cost per chapter. 6 chapters share this height, so ~1/6 of it
       separates one text from the next. Tuned so one mouse-wheel notch
       (~100px) moves you from one text to the next. That also means a notch
       advances ~20 frames of film, so the sequence plays fast — raise this
       for a slower, more cinematic read. */
    .sequence { position: relative; height: 165vh; }

    .stage {
      position: sticky;
      top: 0;
      width: 100%;
      height: 100svh;
      overflow: hidden;
      isolation: isolate;
      background: var(--void);
    }

    #film-canvas {
      position: absolute;
      inset: 0;
      z-index: 0;
      width: 100%;
      height: 100%;
    }

    .stage__wash {
      position: absolute;
      inset: 0;
      z-index: 1;
      background: linear-gradient(180deg, rgba(0, 12, 51, .60), rgba(0, 28, 120, .34) 45%, rgba(0, 28, 120, .26));
      mix-blend-mode: screen;
      opacity: calc(var(--void-mix) * .55);
      pointer-events: none;
    }

    .stage__scrim {
      position: absolute;
      inset: 0;
      z-index: 2;
      background:
        linear-gradient(270deg, rgba(0, 0, 0, .78) 0%, rgba(0, 0, 0, .30) 32%, transparent 58%),
        linear-gradient(0deg, rgba(0, 0, 0, .80) 0%, transparent 34%);
      opacity: var(--scrim-opacity, .74);
      transition: opacity 500ms ease;
      pointer-events: none;
    }

    /* Strong enough to hold white copy over the bright macro pages later on.
       Kept subtle early by the progress-driven opacity. */
    .stage__scrim[data-side="left"] {
      background:
        linear-gradient(90deg, rgba(0, 0, 0, .97) 0%, rgba(0, 0, 0, .92) 26%, rgba(0, 0, 0, .70) 42%, rgba(0, 0, 0, .22) 57%, transparent 70%),
        linear-gradient(0deg, rgba(0, 0, 0, .84) 0%, transparent 30%);
    }

    .stage__grid {
      position: absolute;
      inset: 0 var(--gutter);
      z-index: 3;
      background-image: repeating-linear-gradient(
        90deg,
        transparent 0,
        transparent calc(8.333% - 1px),
        rgba(61, 106, 255, .10) calc(8.333% - 1px),
        rgba(61, 106, 255, .10) 8.333%
      );
      mask-image: linear-gradient(180deg, transparent, black 16%, black 80%, transparent);
      opacity: .45;
      pointer-events: none;
    }

    /* ======================================================================
       06 — CHAPTERS
       ====================================================================== */
    .chapters {
      position: absolute;
      inset: 0;
      z-index: 5;
      pointer-events: none;
    }

    .chapter {
      position: absolute;
      inset: 0;
      display: grid;
      grid-template-columns: repeat(12, minmax(0, 1fr));
      align-content: center;
      column-gap: clamp(10px, 1.2vw, 20px);
      padding: 92px var(--gutter) 104px;
      visibility: hidden;
    }

    .chapter.is-visible { visibility: visible; }
    .chapter__copy { position: relative; align-self: center; }

    .reveal { overflow: hidden; padding-bottom: .1em; }

    .reveal > span {
      display: block;
      transform: translateY(112%);
      transition: transform 760ms var(--ease);
    }

    .chapter.is-visible .reveal > span { transform: translateY(0); }
    .reveal--d1 > span { transition-delay: 90ms; }
    .reveal--d2 > span { transition-delay: 170ms; }

    .chapter__title {
      margin: 0;
      font-family: var(--display);
      /* Sized against real LunaObscura metrics: the longest unbreakable word
         in the deck is PROTOCOLS, which fits its 5-column track up to ~4.15vw.
         Anything larger is silently clipped by .reveal's overflow. */
      font-size: clamp(28px, 3.9vw, 88px);
      font-weight: 700;
      line-height: .92;
      letter-spacing: .015em;
      text-transform: uppercase;
      color: var(--white);
      text-wrap: balance;
      text-shadow: 0 0 30px rgba(0, 28, 120, .85), 0 2px 18px rgba(0, 0, 0, .95);
    }

    .chapter__title em {
      display: block;
      font-style: normal;
      color: var(--blue-lift);
      text-shadow: 0 0 34px rgba(0, 28, 120, .95), 0 2px 18px rgba(0, 0, 0, .95);
    }

    .chapter__deck {
      max-width: 34rem;
      margin: 26px 0 0;
      font-family: var(--body);
      font-size: clamp(15px, 1.15vw, 18px);
      font-weight: 300;
      line-height: 1.62;
      color: var(--ice);
      text-shadow: 0 2px 16px rgba(0, 0, 0, .95);
    }

    .chapter--right .chapter__copy { grid-column: 7 / 13; }
    .chapter--left  .chapter__copy { grid-column: 1 / 6; }
    .chapter--lower-left { align-content: end; }
    .chapter--lower-left .chapter__copy { grid-column: 1 / 6; padding-bottom: 6vh; }

    .chapter--hero .chapter__copy { grid-column: 1 / 7; }
    .chapter--hero .chapter__title { font-size: clamp(34px, 4.0vw, 64px); max-width: 12ch; }
    .chapter--hero .chapter__deck { max-width: 27rem; }

    .chapter--final { align-content: end; }
    .chapter--final .chapter__copy { grid-column: 1 / 7; padding-bottom: 5vh; }

    /* Signature device — the manuscript decodes itself, so does the interface */
    .cipher { display: inline-block; }

    /* ======================================================================
       07 — CTA
       ====================================================================== */
    .cta {
      pointer-events: auto;
      display: inline-flex;
      align-items: center;
      justify-content: space-between;
      gap: 28px;
      min-height: 56px;
      margin-top: 34px;
      padding: 0 22px;
      border: 1px solid var(--blue-lift);
      border-radius: 0;
      background: transparent;
      color: var(--blue-lift);
      font-family: var(--data);
      font-size: 12px;
      letter-spacing: .16em;
      text-decoration: none;
      text-transform: uppercase;
      transition: background 260ms ease, color 260ms ease, box-shadow 260ms ease;
    }

    .cta::after { content: "→"; font-size: 15px; }

    .cta:hover,
    .cta:focus-visible {
      background: rgba(0, 28, 120, .55);
      color: var(--white);
      box-shadow: 0 0 28px rgba(0, 28, 120, .95), inset 0 0 24px rgba(61, 106, 255, .22);
    }

    .cta__note {
      margin: 12px 0 0;
      font-family: var(--data);
      font-size: 10px;
      letter-spacing: .12em;
      text-transform: uppercase;
      color: var(--ice-mute);
    }

    /* ======================================================================
       08 — PROGRESS RAIL
       ====================================================================== */
    .rail {
      position: absolute;
      inset: auto var(--gutter) max(18px, env(safe-area-inset-bottom));
      z-index: 8;
      display: grid;
      grid-template-columns: minmax(90px, 1fr) auto;
      align-items: center;
      gap: 18px;
      color: var(--blue-lift);
      pointer-events: none;
    }

    .rail__fill { transform: scaleX(var(--scroll-progress)); will-change: transform; }

    .rail__cue {
      display: inline-grid;
      grid-template-columns: auto 16px;
      align-items: center;
      gap: 9px;
    }

    .rail__cue::after {
      content: "";
      width: 16px; height: 16px;
      border-right: 1px solid currentColor;
      border-bottom: 1px solid currentColor;
      transform: rotate(45deg) translate(-3px, 3px);
      animation: cue 1.9s ease-in-out infinite;
    }

    @keyframes cue {
      0%, 100% { transform: rotate(45deg) translate(-3px, 3px); opacity: .35; }
      50%      { transform: rotate(45deg) translate(1px, 7px);  opacity: 1; }
    }

    /* ======================================================================
       09 — SECTION 02: AUTHOR DOSSIER
       Ambient looping video backdrop. Deliberately NOT scroll-scrubbed —
       Section 01's canvas engine is untouched by anything in here.
       ====================================================================== */
    .dossier {
      position: relative;
      z-index: 10;
      background: var(--void);
      border-top: 1px solid var(--blue);
    }

    /* Sticky backdrop that takes no layout space, so the eye holds steady
       while the dossier copy scrolls over it instead of stretching to the
       full section height and cropping to a meaningless zoom. */
    .dossier__bg {
      position: sticky;
      top: 0;
      height: 100svh;
      margin-bottom: -100svh;
      overflow: hidden;
      pointer-events: none;
      z-index: 0;
    }

    .dossier__bg video,
    .dossier__bg img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      opacity: .18;
    }

    .dossier__bg::after {
      content: "";
      position: absolute;
      inset: 0;
      background:
        radial-gradient(58% 52% at 50% 46%, rgba(0, 28, 120, .34), transparent 70%),
        radial-gradient(90% 70% at 50% 50%, transparent 30%, rgba(0, 0, 0, .78) 78%),
        linear-gradient(180deg, rgba(0, 0, 0, .92) 0%, rgba(0, 0, 0, .45) 22%, rgba(0, 0, 0, .45) 78%, rgba(0, 0, 0, .92) 100%);
    }

    .dossier__inner {
      position: relative;
      z-index: 1;
      max-width: 1600px;
      margin: 0 auto;
      padding: clamp(96px, 13vw, 190px) var(--gutter) clamp(110px, 15vw, 210px);
    }

    .dossier__title {
      max-width: 19ch;
      margin: 0 0 clamp(40px, 4.5vw, 66px);
      font-family: var(--display);
      font-size: clamp(24px, 2.8vw, 46px);
      font-weight: 700;
      line-height: .95;
      letter-spacing: .015em;
      text-transform: uppercase;
      color: var(--white);
      text-shadow: 0 0 42px rgba(0, 28, 120, .95), 0 0 15px rgba(0, 28, 120, .7), 0 2px 20px rgba(0, 0, 0, .95);
    }

    /* One editorial column, held to a comfortable measure. The hairline picks
       up the accent the removed card borders used to carry. */
    .dossier__bio {
      max-width: 62ch;
      padding-left: clamp(20px, 2.4vw, 40px);
      border-left: 1px solid var(--blue);
    }

    .dossier__bio p {
      margin: 0 0 1.55em;
      font-family: var(--body);
      font-size: clamp(16px, 1.08vw, 19px);
      font-weight: 300;
      line-height: 1.85;
      color: var(--ice);
      text-shadow: 0 2px 18px rgba(0, 0, 0, .95);
    }

    .dossier__bio p:last-child { margin-bottom: 0; }

    /* Sign-off. Needs .dossier__bio prefix to outweigh `.dossier__bio p`. */
    .dossier__bio .dossier__sign {
      margin: clamp(46px, 5vw, 76px) 0 0;
      font-family: var(--display);
      font-size: clamp(17px, 1.5vw, 25px);
      font-weight: 700;
      line-height: 1.2;
      letter-spacing: .14em;
      text-transform: uppercase;
      color: var(--blue-lift);
      text-shadow: 0 0 28px rgba(0, 28, 120, .95), 0 2px 18px rgba(0, 0, 0, .95);
    }

    .dossier__bio .dossier__sign::before {
      content: "";
      display: block;
      width: 54px;
      height: 1px;
      margin-bottom: 22px;
      background: var(--blue-lift);
      box-shadow: 0 0 10px var(--blue-lift);
    }

    /* Visible by default; JS opts in to the entrance so the biography is never
       hidden by a stylesheet the script might fail to undo. */
    .dossier.js-anim .dossier__bio p {
      opacity: 0;
      transform: translateY(18px);
      transition: opacity 800ms var(--ease), transform 800ms var(--ease);
    }

    .dossier.js-anim.is-live .dossier__bio p { opacity: 1; transform: none; }
    .dossier.js-anim.is-live .dossier__bio p:nth-child(2) { transition-delay: 130ms; }
    .dossier.js-anim.is-live .dossier__bio p:nth-child(3) { transition-delay: 260ms; }
    .dossier.js-anim.is-live .dossier__bio p:nth-child(4) { transition-delay: 390ms; }

    /* ======================================================================
       10 — SECTION 03: THE DECK
       Seven brushed-metal cards in a 3D fan. Same tokens, same type system,
       same grid language as Sections 01–02.
       ====================================================================== */
    .deck {
      position: relative;
      z-index: 10;
      background: var(--void);
      border-top: 1px solid var(--blue);
      overflow: hidden;
    }

    /* Same vertical grid used on the film stage, so the chapter continues. */
    .deck::before {
      content: "";
      position: absolute;
      inset: 0 var(--gutter);
      background-image: repeating-linear-gradient(
        90deg,
        transparent 0,
        transparent calc(8.333% - 1px),
        rgba(61, 106, 255, .07) calc(8.333% - 1px),
        rgba(61, 106, 255, .07) 8.333%
      );
      mask-image: linear-gradient(180deg, transparent, black 12%, black 84%, transparent);
      opacity: .5;
      pointer-events: none;
    }

    .deck__inner {
      position: relative;
      max-width: 1600px;
      margin: 0 auto;
      padding: clamp(96px, 12vw, 180px) var(--gutter) clamp(110px, 14vw, 200px);
    }

    .deck__title {
      max-width: 17ch;
      margin: 0 0 clamp(26px, 3vw, 40px);
      font-family: var(--display);
      font-size: clamp(24px, 3.1vw, 52px);
      font-weight: 700;
      line-height: .95;
      letter-spacing: .015em;
      text-transform: uppercase;
      color: var(--white);
      text-shadow: 0 0 42px rgba(0, 28, 120, .95), 0 0 15px rgba(0, 28, 120, .7);
    }

    .deck__quote {
      max-width: 46ch;
      margin: 0;
      padding-left: clamp(18px, 2vw, 32px);
      border-left: 1px solid var(--blue);
      font-family: var(--body);
      font-size: clamp(15px, 1.05vw, 18px);
      font-weight: 300;
      line-height: 1.75;
      color: rgba(226, 232, 240, .82);
    }

    /* Title and quote arrive before the deck, same opt-in pattern as the
       dossier: visible by default, JS opts in to the entrance. */
    .deck.js-anim .deck__title,
    .deck.js-anim .deck__quote {
      opacity: 0;
      transform: translateY(16px);
      transition: opacity 820ms var(--ease), transform 820ms var(--ease);
    }

    .deck.js-anim.is-live .deck__title,
    .deck.js-anim.is-live .deck__quote { opacity: 1; transform: none; }
    .deck.js-anim.is-live .deck__quote { transition-delay: 160ms; }

    .deck__stage {
      position: relative;
      height: clamp(430px, 46vw, 660px);
      margin-top: clamp(56px, 6vw, 96px);
      perspective: 1800px;
      perspective-origin: 50% 45%;
    }

    .deck__fan {
      position: absolute;
      inset: 0;
      /* perspective + flat, NOT preserve-3d. preserve-3d puts all seven cards
         in one shared 3D space, so a tilted or flipping card's plane
         intersects its neighbour's and the browser draws the crossing as a
         hard rectangular slice through both. Flat gives each card its own
         perspective projection and composites them by z-index, so they can
         never cut into one another — while each card still flips in real 3D,
         because .pcard keeps preserve-3d for its own faces. */
      perspective: 1800px;
      transform-style: flat;
      /* The fan covers the whole stage at z=0. Without this it swallows every
         pointer event aimed at a card, leaving the deck unclickable. */
      pointer-events: none;
      /* Always fanned. Narrower viewports shrink the spread rather than
         compressing the deck, so all seven cards stay distinguishable and
         the fan never overflows the section. */
      --spread: 1;
    }

    @media (max-width: 1250px) { .deck__fan { --spread: .82; } }
    @media (max-width: 1040px) { .deck__fan { --spread: .68; } }

    /* --- one card ------------------------------------------------------- */
    .pcard {
      --s: .78;
      --lift: 0;
      --push: 0;
      --pop: 0;
      --tilt-x: 0;
      --tilt-y: 0;

      position: absolute;
      left: 50%;
      top: 50%;
      width: var(--card-w);
      height: var(--card-h);
      margin: calc(var(--card-h) / -2) 0 0 calc(var(--card-w) / -2);
      padding: 0;
      border: 0;
      background: transparent;
      cursor: pointer;
      pointer-events: auto;   /* re-enable, the fan opts out above */
      transform-style: preserve-3d;
      transform:
        translate3d(
          calc((var(--tx) * var(--spread) + var(--push) * 12) * 1px),
          calc((var(--ty) * var(--spread) + var(--lift)) * 1px),
          calc((var(--tz) * var(--spread) + var(--pop)) * 1px))
        rotateZ(calc(var(--rz) * var(--spread) * 1deg))
        rotateX(calc(var(--tilt-x) * 1deg))
        rotateY(calc(var(--tilt-y) * 1deg))
        scale(var(--s));
      transition: transform 620ms var(--ease), opacity 500ms ease, filter 500ms ease;
      will-change: transform;
    }

    /* In a flat context translateZ no longer decides what paints on top, so
       the fan's stacking is explicit: centre card highest, falling away to
       the edges. */
    .pcard:nth-child(1), .pcard:nth-child(7) { z-index: 1; }
    .pcard:nth-child(2), .pcard:nth-child(6) { z-index: 2; }
    .pcard:nth-child(3), .pcard:nth-child(5) { z-index: 3; }
    .pcard:nth-child(4)                      { z-index: 4; }

    .pcard:focus { outline: none; }

    .pcard:hover:not(.is-open),
    .pcard:focus-visible:not(.is-open) {
      --s: .84;
      --lift: -25;
      --pop: 40;
      z-index: 20;
    }

    .pcard:focus-visible .pcard__face--back { box-shadow: 0 0 0 2px var(--blue-lift), 0 0 26px rgba(0, 28, 120, .9); }

    /* display:block is load-bearing — this is a <span>, and as an inline box
       it ignores width/height, collapsing to 0x0 and taking the absolutely
       positioned faces with it. */
    .pcard__inner {
      display: block;
      position: relative;
      width: 100%;
      height: 100%;
      transform-style: preserve-3d;
      transition: transform 1100ms cubic-bezier(.62, .03, .27, 1);
    }

    .pcard.is-open .pcard__inner { transform: rotateY(180deg); }

    .pcard__face {
      position: absolute;
      inset: 0;
      backface-visibility: hidden;
      -webkit-backface-visibility: hidden;
      background-size: cover;
      background-position: center;
      border-radius: 3.4% / 2.1%;
      box-shadow: 0 26px 60px rgba(0, 0, 0, .9);
    }

    .pcard__face--back  { background-image: url("media/card-back-2.webp"); }
    .pcard__face--front { background-image: url("media/card-front-2.webp"); transform: rotateY(180deg); }

    /* The navy stock is even, so this only deepens the centre a little to sit
       the copy off the card texture. The old heavy vignette existed to beat a
       bright brushed-metal highlight that no longer exists. */
    .pcard__face--front::before {
      content: "";
      position: absolute;
      inset: 0;
      border-radius: inherit;
      background: radial-gradient(ellipse 88% 84% at 50% 50%,
        rgba(0, 0, 0, .46) 0%, rgba(0, 0, 0, .38) 58%, rgba(0, 0, 0, .16) 82%, transparent 100%);
    }

    /* Specular highlight that follows the cursor. Deliberately a soft radial
       with no blend mode: the previous linear band with mix-blend-mode:screen
       brightened the black behind the card and read as a hard-edged triangle
       across the corner. */
    .pcard__sheen {
      position: absolute;
      inset: 0;
      border-radius: inherit;
      background: radial-gradient(circle at var(--mx, 50%) var(--my, 40%),
        rgba(255, 255, 255, .17) 0%, rgba(255, 255, 255, .07) 26%, transparent 58%);
      opacity: 0;
      transition: opacity 320ms ease;
      pointer-events: none;
    }

    .pcard:hover .pcard__sheen { opacity: 1; }

    .pcard__content {
      position: absolute;
      /* Inside the silver frame — at 10%/9% the copy overlapped the ornament. */
      inset: 14% 12%;
      display: flex;
      flex-direction: column;
      justify-content: center;
      text-align: left;
    }

    .pcard__label,
    .pcard__name,
    .pcard__quote,
    .pcard__body,
    .pcard__rule {
      opacity: 0;
      transform: translateY(10px);
      transition: opacity 620ms var(--ease), transform 620ms var(--ease);
    }

    .pcard.is-open .pcard__label,
    .pcard.is-open .pcard__name,
    .pcard.is-open .pcard__quote,
    .pcard.is-open .pcard__body,
    .pcard.is-open .pcard__rule { opacity: 1; transform: none; }

    .pcard.is-open .pcard__label { transition-delay: 620ms; }
    .pcard.is-open .pcard__name  { transition-delay: 760ms; }
    .pcard.is-open .pcard__quote { transition-delay: 900ms; }
    .pcard.is-open .pcard__body  { transition-delay: 1040ms; }
    .pcard.is-open .pcard__rule  { transition-delay: 1180ms; }

    .pcard__label {
      margin: 0 0 14px;
      font-family: var(--data);
      font-size: 10px;
      letter-spacing: .3em;
      text-transform: uppercase;
      color: var(--card-ink);
    }

    .pcard__name {
      margin: 0 0 16px;
      font-family: var(--display);
      font-size: 26px;
      font-weight: 700;
      line-height: .96;
      letter-spacing: .03em;
      text-transform: uppercase;
      color: var(--white);
      text-shadow: 0 0 24px rgba(0, 28, 120, .9);
    }

    .pcard__quote {
      margin: 0 0 14px;
      font-family: var(--body);
      font-size: 12.5px;
      font-weight: 400;
      line-height: 1.48;
      color: rgba(255, 255, 255, .94);
    }

    .pcard__body {
      margin: 0;
      font-family: var(--body);
      font-size: 11px;
      font-weight: 300;
      line-height: 1.6;
      color: rgba(226, 232, 240, .66);
    }

    /* No Q/A markers — the weight and colour difference between the question
       and the answer already separates them. */

    .pcard__rule {
      display: block;
      width: 100%;
      height: 1px;
      margin-top: 20px;
      background: linear-gradient(90deg, var(--card-ink), rgba(201, 214, 236, 0));
      transform-origin: left;
    }

    /* Cards that are not the open one recede and darken — enough to push them
       back without blacking them out. */
    .deck__fan.has-open .pcard:not(.is-open) {
      --pop: -220;
      --s: .68;
      filter: brightness(.62);
      pointer-events: none;
    }

    .pcard.is-open {
      --s: 1;
      --lift: 0;
      --push: 0;
      --pop: 200;
      --tilt-x: 0;
      --tilt-y: 0;
      transform:
        translate3d(0, 0, 200px)
        rotateZ(0deg)
        scale(var(--s));
      z-index: 60;
      cursor: default;
    }

    .deck__close {
      position: absolute;
      top: 8px;
      right: 8px;
      z-index: 70;
      width: 44px;
      height: 44px;
      padding: 0;
      border: 0;
      background: transparent;
      cursor: pointer;
      opacity: 0;
      pointer-events: none;
      transition: opacity 400ms ease;
    }

    .deck__fan.has-open ~ .deck__close,
    .deck__stage.has-open .deck__close { opacity: 1; pointer-events: auto; }

    .deck__close::before,
    .deck__close::after {
      content: "";
      position: absolute;
      left: 50%;
      top: 50%;
      width: 19px;
      height: 1px;
      margin: 0 0 0 -9.5px;
      background: var(--blue-lift);
      box-shadow: 0 0 8px var(--blue-lift);
    }

    .deck__close::before { transform: rotate(45deg); }
    .deck__close::after  { transform: rotate(-45deg); }
    .deck__close:focus-visible { outline: 1px solid var(--blue-lift); outline-offset: 4px; }

    .deck__counter {
      display: none;
      margin-top: 26px;
      font-family: var(--data);
      font-size: 11px;
      letter-spacing: .28em;
      color: var(--blue-lift);
    }

    /* ======================================================================
       11 — SECTION 04: THE CODEX
       33 chapters in six sections. The mask is driven by CHAPTER STATE, not
       scroll distance: it holds for every chapter inside a section and only
       swaps at the six boundaries.

       Why sprites and not the supplied video: a video can only be scrubbed by
       time, so it cannot hold one mask while the reader moves through nine
       chapters and then swap on demand. The six masks are cut as sprites from
       the reference art and swapped with the same physical left/right 3D
       language the video demonstrates.
       ====================================================================== */
    .codex {
      /* Same shape as the film section: one tall block with a sticky stage,
         and scroll POSITION drives everything.

         33 slots, one per chapter, nothing in between. One notch moves one
         chapter for a single reason: the slot IS the size of a wheel notch.
         Measured off a screen recording — three notches moved exactly 504px,
         so a notch here is 168px, Windows set to scroll 5 lines rather than
         the default 3.

         There is no CSS scroll snapping, on purpose. It was tried, it was
         redundant once the slot matched the notch, and it locked the wheel up
         — see the note on .codex__snap. The section's own measurement still
         has to be right, though: see measure() in the script.

         The mask swap gets no scroll distance of its own; it plays off the
         frame easing in the script the moment a boundary is crossed, so it
         runs as one whole animation instead of being smeared across notches.

         --codex-slot is the dial. If one notch still moves two chapters,
         double it; if it takes two notches to move one, halve it. */
      --codex-slot: 168px;
      height: calc(33 * var(--codex-slot) + 100svh);
      position: relative;
      z-index: 10;
      background: var(--void);
      border-top: 1px solid var(--blue);
    }



    .codex::before {
      content: "";
      position: absolute;
      inset: 0 var(--gutter);
      background-image: repeating-linear-gradient(
        90deg,
        transparent 0,
        transparent calc(8.333% - 1px),
        rgba(61, 106, 255, .07) calc(8.333% - 1px),
        rgba(61, 106, 255, .07) 8.333%
      );
      mask-image: linear-gradient(180deg, transparent, black 4%, black 96%, transparent);
      opacity: .5;
      pointer-events: none;
    }

    /* Full-bleed cinematic viewport. The video IS the section — no card, no
       frame, no column. Copy floats on top of it. */
    .codex__stage {
      position: sticky;
      top: 0;
      height: 100svh;
      /* clip, NOT hidden: `hidden` makes this a scroll container, so a wheel
         event landing on it is handled here first instead of scrolling the
         page. `clip` crops identically without ever becoming a scroller. */
      overflow: clip;
      /* Nothing on this stage is interactive — the canvas, the scrim, the
         copy and the index are all decorative duplicates of the <ol> below.
         Taking the whole stage out of hit-testing means a wheel anywhere in
         the section reaches the document, not just over the canvas. */
      pointer-events: none;
      background: var(--void);
    }

    /* The measuring rod. 33 slots tall, absolutely positioned from the TOP of
       the section, laid over the sticky stage so it adds no height. The script
       reads its height as the exact scroll span of the 33 chapters, which is
       what keeps chapter boundaries and scroll offsets in lockstep no matter
       what --codex-slot is set to.

       It used to carry CSS scroll snap points as well. It no longer does —
       see the note on .codex__snap below. */
    .codex__track {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: calc(33 * var(--codex-slot));
      pointer-events: none;
    }

    /* NO scroll snapping. This is deliberate, and it is what fixed the wheel
       locking up unless the mouse was moving.

       Chrome latches a wheel gesture onto one scroll container, and a
       mandatory snap container re-snaps after every wheel event. Because the
       slot is sized to exactly one wheel notch, the scroll already landed on
       a snap point each time — so the snap machinery spent its life re-snapping
       to the position it was already at, swallowing the next notch. Nothing
       moved until some other input broke the latch, which is why wiggling the
       cursor freed it. scroll-snap-stop: always made it worse, and never
       worked on the wheel in the first place.

       Removing it costs nothing, because snapping had already become
       cosmetic: resolve() rounds to the NEAREST slot, so every scroll
       position already shows the chapter it is closest to. Resting mid-slot
       and resting on a boundary render identically. One notch still moves
       exactly one chapter — that comes from --codex-slot matching the notch,
       not from snapping. */
    .codex__snap {
      display: block;
      height: var(--codex-slot);
    }

    /* A canvas frame sequence, not a <video>. Video can only be driven by
       play/pause or by seeking, so an interrupted transition has to stop and
       restart — which is the cut. A frame sequence is a pure function of
       scroll position: reversing mid-swap just means the function returns a
       lower frame, so it flows straight back from wherever it is. Same engine
       as the film section at the top of the page. */
    .codex__canvas {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      display: block;
      pointer-events: none;
      will-change: transform;
      transform-origin: 50% 45%;
    }


    /* Readability only. Weighted to the left, where the copy sits, and gone
       by the middle so the figure is never veiled. */
    .codex__scrim {
      position: absolute;
      inset: 0;
      pointer-events: none;
      background:
        linear-gradient(90deg, rgba(0,0,0,.94) 0%, rgba(0,0,0,.86) 20%, rgba(0,0,0,.46) 40%, rgba(0,0,0,.10) 56%, transparent 68%),
        linear-gradient(0deg, rgba(0,0,0,.72) 0%, transparent 26%),
        linear-gradient(180deg, rgba(0,0,0,.55) 0%, transparent 22%);
    }

    .codex__overlay {
      position: absolute;
      inset: 0;
      display: grid;
      align-content: center;
      max-width: 1600px;
      margin: 0 auto;
      padding: 0 var(--gutter);
      pointer-events: none;
    }

    /* --- the chapter panel ---------------------------------------------- */
    .codex__panel { position: relative; max-width: 32rem; }

    .codex__meta {
      display: flex;
      align-items: center;
      gap: 14px;
      margin: 0 0 clamp(18px, 2vw, 28px);
      font-family: var(--data);
      font-size: 10px;
      letter-spacing: .26em;
      text-transform: uppercase;
      color: var(--blue-lift);
    }

    .codex__meta::after {
      content: "";
      flex: 1;
      height: 1px;
      background: linear-gradient(90deg, var(--blue), transparent);
    }

    .codex__part {
      margin: 0 0 clamp(14px, 1.6vw, 22px);
      font-family: var(--data);
      font-size: 10px;
      letter-spacing: .2em;
      text-transform: uppercase;
      color: rgba(226, 232, 240, .5);
    }

    .codex__num {
      display: block;
      margin: 0 0 6px;
      font-family: var(--display);
      font-size: clamp(34px, 4.4vw, 74px);
      font-weight: 700;
      line-height: .88;
      letter-spacing: .02em;
      color: rgba(61, 106, 255, .34);
    }

    .codex__title {
      margin: 0 0 clamp(16px, 1.8vw, 26px);
      font-family: var(--display);
      font-size: clamp(22px, 2.5vw, 42px);
      font-weight: 700;
      line-height: .98;
      letter-spacing: .015em;
      text-transform: uppercase;
      color: var(--white);
      text-shadow: 0 0 38px rgba(0, 28, 120, .9);
    }

    .codex__desc {
      margin: 0;
      font-family: var(--body);
      font-size: clamp(14px, 1.02vw, 17px);
      font-weight: 300;
      line-height: 1.72;
      color: rgba(226, 232, 240, .72);
    }

    /* --- the index ------------------------------------------------------- */
    .codex__index {
      position: absolute;
      z-index: 3;
      left: var(--gutter);
      right: var(--gutter);
      bottom: clamp(18px, 3vh, 34px);
      display: flex;
      align-items: center;
      gap: 16px;
      font-family: var(--data);
      font-size: 10px;
      letter-spacing: .2em;
      text-transform: uppercase;
      color: var(--blue-lift);
    }

    .codex__count { min-width: 8ch; }

    .codex__rule {
      flex: 1;
      height: 1px;
      background: linear-gradient(90deg, var(--blue), rgba(0, 28, 120, 0));
    }

    /* Same cue language as the film section at the top of the page. */
    .codex__cue {
      display: inline-grid;
      grid-template-columns: auto 16px;
      align-items: center;
      gap: 9px;
      color: var(--blue-lift);
      white-space: nowrap;
    }

    .codex__cue::after {
      content: "";
      width: 16px; height: 16px;
      border-right: 1px solid currentColor;
      border-bottom: 1px solid currentColor;
      transform: rotate(45deg) translate(-3px, 3px);
      animation: cue 1.9s ease-in-out infinite;
    }

    /* Once the reader is moving, the prompt has done its job. */
    .codex__cue { transition: opacity 500ms ease; }
    .codex.has-moved .codex__cue { opacity: 0; }

    /* --- no-JS fallback: the chapters are real content, always present ---- */
    .codex__source {
      max-width: 62rem;
      margin: 0 auto;
      padding: clamp(80px, 10vw, 140px) var(--gutter);
      list-style: none;
      counter-reset: ch;
    }

    .codex__source li { margin: 0 0 clamp(30px, 4vw, 54px); padding-left: 68px; position: relative; }

    .codex__source li::before {
      counter-increment: ch;
      content: counter(ch, decimal-leading-zero);
      position: absolute;
      left: 0;
      top: 2px;
      font-family: var(--data);
      font-size: 11px;
      letter-spacing: .18em;
      color: var(--blue-lift);
    }

    .codex__source h3 {
      margin: 0 0 10px;
      font-family: var(--display);
      font-size: clamp(18px, 2vw, 26px);
      font-weight: 700;
      line-height: 1;
      text-transform: uppercase;
      color: var(--white);
    }

    .codex__source p {
      margin: 0;
      font-family: var(--body);
      font-size: 15px;
      font-weight: 300;
      line-height: 1.7;
      color: rgba(226, 232, 240, .7);
    }

    /* JS confirmed: hide the plain list, show the cinematic stage. */
    /* Hidden visually, NOT removed: the stage is decorative duplication, so
       assistive tech reads this list instead. */
    .codex.js-mode .codex__source {
      position: absolute;
      width: 1px; height: 1px;
      padding: 0; margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
    }
    .codex:not(.js-mode) { height: auto; }
    .codex:not(.js-mode) .codex__stage { display: none; }

    /* ======================================================================
       12 — THE GATE (scroll invitation, sits just before the codex)
       Same section furniture as every other block: black ground, hairline
       blue top rule, the shared vertical grid.
       ====================================================================== */
    .gate {
      position: relative;
      z-index: 10;
      background: var(--void);
      border-top: 1px solid var(--blue);
    }

    .gate::before {
      content: "";
      position: absolute;
      inset: 0 var(--gutter);
      background-image: repeating-linear-gradient(
        90deg,
        transparent 0,
        transparent calc(8.333% - 1px),
        rgba(61, 106, 255, .07) calc(8.333% - 1px),
        rgba(61, 106, 255, .07) 8.333%
      );
      mask-image: linear-gradient(180deg, transparent, black 24%, black 76%, transparent);
      opacity: .5;
      pointer-events: none;
    }

    .gate__inner {
      position: relative;
      max-width: 1600px;
      margin: 0 auto;
      padding: clamp(92px, 12vw, 170px) var(--gutter) clamp(70px, 9vw, 120px);
      text-align: center;
    }

    .gate__title {
      max-width: 22ch;
      margin: 0 auto;
      font-family: var(--display);
      font-size: clamp(21px, 2.5vw, 40px);
      font-weight: 700;
      line-height: 1.02;
      letter-spacing: .015em;
      text-transform: uppercase;
      color: var(--white);
      text-shadow: 0 0 38px rgba(0, 28, 120, .9);
    }

    .gate__sub {
      margin: clamp(16px, 2vw, 24px) 0 0;
      color: var(--blue-lift);
    }

    /* The signal: a hairline that a single pulse travels down, once every few
       seconds. No arrow, no bounce — it only says "the page goes that way". */
    .gate__signal {
      position: relative;
      width: 1px;
      height: clamp(64px, 8vh, 104px);
      margin: clamp(30px, 4vw, 48px) auto 0;
      background: linear-gradient(180deg, var(--blue) 0%, rgba(0, 28, 120, 0) 100%);
      overflow: hidden;
    }

    .gate__signal::after {
      content: "";
      position: absolute;
      inset: 0 auto auto 0;
      width: 1px;
      height: 38%;
      background: linear-gradient(180deg, rgba(61, 106, 255, 0), var(--blue-lift));
      animation: gate-drop 3.2s cubic-bezier(.55, 0, .45, 1) infinite;
    }

    @keyframes gate-drop {
      0%   { transform: translateY(-110%); opacity: 0; }
      18%  { opacity: 1; }
      72%  { opacity: 1; }
      100% { transform: translateY(300%); opacity: 0; }
    }

    /* ======================================================================
       13 — THE CLOSING STATEMENT
       ====================================================================== */
    .close {
      position: relative;
      z-index: 10;
      background: var(--void);
      border-top: 1px solid var(--blue);
    }

    .close::before {
      content: "";
      position: absolute;
      inset: 0 var(--gutter);
      background-image: repeating-linear-gradient(
        90deg,
        transparent 0,
        transparent calc(8.333% - 1px),
        rgba(61, 106, 255, .07) calc(8.333% - 1px),
        rgba(61, 106, 255, .07) 8.333%
      );
      mask-image: linear-gradient(180deg, transparent, black 10%, black 88%, transparent);
      opacity: .5;
      pointer-events: none;
    }

    .close__inner {
      position: relative;
      max-width: 1600px;
      margin: 0 auto;
      padding: clamp(110px, 15vw, 210px) var(--gutter) clamp(96px, 12vw, 170px);
    }

    /* Sized to the family the rest of the page already uses — the deck tops
       out at 52px and the dossier at 46px, so the old 86px was reading as a
       different website. Still the largest heading on the page, because it is
       the last one. */
    .close__title {
      max-width: 24ch;
      margin: 0 0 clamp(40px, 5vw, 70px);
      font-family: var(--display);
      font-size: clamp(26px, 3.4vw, 58px);
      font-weight: 700;
      line-height: .96;
      letter-spacing: .015em;
      text-transform: uppercase;
      color: var(--white);
      text-shadow: 0 0 38px rgba(0, 28, 120, .9);
    }

    .close__cols {
      display: grid;
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
      gap: clamp(34px, 5vw, 88px);
      align-items: start;
      max-width: 1180px;
    }

    .close__body p,
    .close__notice p {
      margin: 0 0 18px;
      font-family: var(--body);
      font-size: clamp(15px, 1.06vw, 18px);
      font-weight: 300;
      line-height: 1.74;
      color: rgba(226, 232, 240, .72);
    }

    .close__body p:last-child,
    .close__notice p:last-child { margin-bottom: 0; }

    /* The turn in the argument — it gets the white and the rule. */
    .close__hold {
      margin-top: clamp(22px, 2.6vw, 34px) !important;
      padding-top: clamp(22px, 2.6vw, 34px);
      border-top: 1px solid var(--blue);
      color: var(--white) !important;
    }

    .close__notice {
      padding-left: clamp(18px, 2vw, 30px);
      border-left: 1px solid var(--blue);
    }

    /* The CTA follows the copy directly now; it carries its own 34px
       margin-top from .cta, so this only tops it up to a section-sized gap.

       Centred on the section's full inner width, which is itself centred in
       the viewport — so the button lands on the true middle of the page, not
       the middle of the copy block above it. */
    .close__act {
      display: flex;
      justify-content: center;
      margin-top: clamp(26px, 3.4vw, 52px);
    }

    /* ======================================================================
       14 — FOOTER
       ====================================================================== */
    .foot {
      position: relative;
      z-index: 10;
      background: var(--void);
      border-top: 1px solid var(--blue);
    }

    .foot__inner {
      position: relative;
      max-width: 1600px;
      margin: 0 auto;
      /* Enough bottom padding that the closing rule is not flush against the
         edge of the window, and no more — the long black runway is gone. */
      padding: clamp(56px, 7vw, 96px) var(--gutter) clamp(34px, 4vw, 56px);
    }

    /* The brand line opens the footer now, so it takes no rule of its own —
       the footer's own top border is already the divider. */
    .foot__brand {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      gap: clamp(14px, 2vw, 28px);
    }

    .foot__sig {
      margin: 0;
      font-family: var(--display);
      font-size: clamp(18px, 2.1vw, 32px);
      font-weight: 700;
      line-height: 1;
      letter-spacing: .015em;
      text-transform: uppercase;
      color: var(--white);
    }

    .foot__author {
      margin: 0;
      color: var(--ice-mute);
    }

    /* ---- payment methods ------------------------------------------------
       Wordmarks in the site's own type, not the brands' logo artwork: the
       card networks' marks are trademarks with fixed colours, so a recoloured
       copy would be both a licensing problem and off-palette here. Flat blue
       plates match the rest of the page and say the same thing. */
    .foot__pay {
      margin-top: clamp(44px, 5.5vw, 76px);
      padding-top: clamp(26px, 3vw, 40px);
      border-top: 1px solid var(--blue);
    }

    .foot__pay-label {
      margin: 0 0 clamp(14px, 1.6vw, 20px);
      letter-spacing: .26em;
      color: var(--blue-lift);
    }

    .paylist {
      display: flex;
      flex-wrap: wrap;
      gap: clamp(8px, .9vw, 12px);
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .paycard {
      display: flex;
      align-items: center;
      justify-content: center;
      min-width: 92px;
      height: 48px;
      padding: 0 16px;
      border: 1px solid var(--blue);
      background: rgba(0, 28, 120, .14);
    }

    /* One optical height for every mark; width follows each logo's own
       proportions rather than being forced into a common box. */
    .paymark {
      display: block;
      height: 21px;
      width: auto;
    }

    /* The two boxed marks read smaller at the same height, so they get a
       little back. */
    .paycard:nth-child(3) .paymark { height: 26px; }

    .foot__pay-note {
      margin: clamp(14px, 1.6vw, 20px) 0 0;
      font-family: var(--body);
      font-size: 13px;
      font-weight: 300;
      line-height: 1.7;
      color: var(--ice-mute);
    }

    .foot__legal {
      margin: clamp(22px, 2.6vw, 34px) 0 0;
      max-width: 64ch;
      color: var(--ice-mute);
      text-transform: none;
      letter-spacing: .06em;
    }

    /* The page stops on this line. */
    .foot__end { margin-top: clamp(40px, 5vw, 68px); }

    .foot__rule {
      height: 1px;
      background: var(--blue);
    }

    /* ---- entrance: opt-in, so nothing is ever hidden without JS ---------- */
    .close.js-anim .close__title,
    .close.js-anim .close__cols,
    .close.js-anim .close__act,
    .foot.js-anim .foot__brand,
    .foot.js-anim .foot__pay,
    .foot.js-anim .foot__end {
      opacity: 0;
      transform: translateY(16px);
      transition: opacity 820ms var(--ease), transform 820ms var(--ease);
    }

    .close.js-anim.is-live .close__title,
    .close.js-anim.is-live .close__cols,
    .close.js-anim.is-live .close__act,
    .foot.js-anim.is-live .foot__brand,
    .foot.js-anim.is-live .foot__pay,
    .foot.js-anim.is-live .foot__end { opacity: 1; transform: none; }

    .close.js-anim.is-live .close__cols { transition-delay: 150ms; }
    .close.js-anim.is-live .close__act  { transition-delay: 300ms; }
    .foot.js-anim.is-live .foot__pay    { transition-delay: 140ms; }
    .foot.js-anim.is-live .foot__end    { transition-delay: 260ms; }

    /* ======================================================================
       15 — MOBILE ART DIRECTION
       ====================================================================== */
    @media (max-width: 780px) {
      :root { --gutter: 20px; }

      /* Kept longer than desktop on purpose: touch momentum covers far more
         distance per gesture, so a desktop-short page gets flicked past. */
      .sequence { height: 200vh; }

      .stage__scrim,
      .stage__scrim[data-side="left"] {
        background:
          linear-gradient(180deg, rgba(0, 0, 0, .70), transparent 22%),
          linear-gradient(0deg, rgba(0, 0, 0, .97) 0%, rgba(0, 0, 0, .92) 30%, rgba(0, 0, 0, .70) 46%, rgba(0, 0, 0, .28) 58%, transparent 70%);
      }

      .stage__grid { opacity: .24; }

      .chapter {
        grid-template-columns: repeat(6, minmax(0, 1fr));
        align-content: end;
        padding: 72px var(--gutter) 96px;
      }

      .chapter__copy,
      .chapter--hero .chapter__copy,
      .chapter--right .chapter__copy,
      .chapter--left .chapter__copy,
      .chapter--lower-left .chapter__copy,
      .chapter--final .chapter__copy {
        grid-column: 1 / 7;
        padding-bottom: 0;
      }

      .chapter__title,
      .chapter--hero .chapter__title { font-size: clamp(28px, 8.6vw, 46px); max-width: none; }
      .chapter__deck { margin-top: 16px; font-size: 15px; max-width: 26rem; }
      .cta { margin-top: 24px; width: 100%; }

      .rail { grid-template-columns: 1fr; gap: 0; }
      .rail__cue { display: none; }
      .loader__glyphs { font-size: 11px; }

      .dossier__title { font-size: clamp(23px, 6.4vw, 34px); max-width: none; }
      .dossier__bg video, .dossier__bg img { opacity: .15; }
      .dossier__bio { padding-left: 18px; max-width: none; }
      .dossier__bio p { font-size: 16px; line-height: 1.78; }

      /* --- Deck: a dedicated swipe layout, not a shrunken fan ----------- */
      :root { --card-w: min(74vw, 300px); }

      .deck__title { font-size: clamp(23px, 6.4vw, 34px); max-width: none; }
      .deck__quote { font-size: 15px; padding-left: 16px; }

      .deck__stage {
        height: auto;
        margin-top: 40px;
        perspective: 1400px;
      }

      /* Neighbouring cards peek in at both edges; snap centres the active one,
         which is also what "moves it to centre" on tap — no JS positioning. */
      .deck__fan {
        position: static;
        display: flex;
        gap: 18px;
        overflow-x: auto;
        /* Here the fan IS the swipe surface, so it must take pointer events
           back — the desktop rule disables them to stop it eating card taps. */
        pointer-events: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x mandatory;
        padding: 12px calc(50% - var(--card-w) / 2) 20px;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
      }

      .deck__fan::-webkit-scrollbar { display: none; }

      .pcard {
        position: static;
        flex: 0 0 var(--card-w);
        width: var(--card-w);
        height: var(--card-h);
        margin: 0;
        scroll-snap-align: center;
        transform: none;
        --s: 1;
      }

      /* No hover on touch: the pointer-tilt and lift are desktop-only. */
      .pcard:hover:not(.is-open) { --s: 1; --lift: 0; --pop: 0; transform: none; }
      .pcard__sheen { display: none; }

      .deck__fan.has-open { overflow-x: hidden; }
      .deck__fan.has-open .pcard:not(.is-open) { filter: brightness(.58); --s: 1; transform: none; }
      .pcard.is-open { transform: none; --s: 1; }

      .deck__counter { display: block; }
      .deck__close { top: 0; right: 0; }

      /* --- Codex: figure up top, copy over the lower band --------------- */
      /* Kept longer than desktop: a touch flick covers far more distance than
         a wheel notch, so a desktop-short slot gets skimmed straight past. */
      .codex { --codex-slot: 150px; }

      /* contain, not cover: a 16:9 frame cover-cropped into a tall phone
         viewport shows only the middle ~26% and cuts the hood off at both
         sides. Contained, the figure survives whole and the letterbox just
         reads as more black page. */
      .codex__video {
        object-fit: contain;
        object-position: center 30%;
      }

      .codex__scrim {
        background:
          linear-gradient(0deg, rgba(0,0,0,.95) 0%, rgba(0,0,0,.9) 30%, rgba(0,0,0,.45) 48%, transparent 64%),
          linear-gradient(180deg, rgba(0,0,0,.5) 0%, transparent 16%);
      }

      .codex__overlay {
        align-content: end;
        padding-bottom: 13vh;
      }

      .codex__panel { max-width: none; }
      .codex__num { font-size: clamp(30px, 11vw, 52px); }
      .codex__title { font-size: clamp(20px, 6.4vw, 30px); }
      .codex__desc { font-size: 15px; line-height: 1.68; }
      .codex__meta { margin-bottom: 14px; }

      .codex__index { gap: 10px; bottom: max(12px, env(safe-area-inset-bottom)); }
      .codex__count { min-width: 7ch; }
      /* Six hit areas across a phone width are too small to aim at. */
      .codex__parts { display: none; }

      /* --- Gate ---------------------------------------------------------- */
      .gate__inner { padding: 78px var(--gutter) 62px; }
      .gate__title { font-size: clamp(20px, 6.6vw, 30px); max-width: 18ch; }
      .gate__sub { font-size: 9px; letter-spacing: .12em; }
      .gate__signal { height: 60px; }

      /* --- Closing statement ---------------------------------------------
         One column, and the notice's left rule becomes a top rule — a
         vertical divider between stacked blocks reads as an accident. */
      .close__inner { padding: 92px var(--gutter) 84px; }
      /* No ch cap here — at phone width the column is already the limit, and
         a 13ch cap on top of it chopped the heading into five short lines. */
      .close__title {
        font-size: clamp(23px, 7.4vw, 34px);
        max-width: none;
        margin-bottom: 34px;
      }
      .close__cols { grid-template-columns: minmax(0, 1fr); gap: 34px; }
      .close__body p,
      .close__notice p { font-size: 15px; line-height: 1.72; max-width: 46ch; }
      .close__notice {
        padding: 30px 0 0;
        border-left: 0;
        border-top: 1px solid var(--blue);
      }
      /* The CTA is full-width on phones everywhere else on the page. */
      .close__act .cta { width: 100%; }

      /* --- Footer -------------------------------------------------------- */
      .foot__inner { padding: 52px var(--gutter) 34px; }
      .foot__brand { gap: 8px; }
      .foot__sig { font-size: clamp(17px, 5.6vw, 24px); }
      /* Five plates across a phone would be 60px each — let them wrap, and
         let each one earn its width from its own label. */
      .paylist { gap: 7px; }
      .paycard {
        flex: 0 1 auto;
        min-width: 0;
        height: 42px;
        padding: 0 12px;
      }
      .paymark { height: 17px; }
      .paycard:nth-child(3) .paymark { height: 22px; }
      .foot__pay { margin-top: 40px; }
      .foot__pay-note { font-size: 12.5px; }
      .foot__end { margin-top: 38px; }
    }

    /* ======================================================================
       10 — REDUCED MOTION
       ====================================================================== */
    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
      }
      .sequence { height: 100svh; }
      .chapter:not(.chapter--hero) { display: none; }
      .chapter--hero { visibility: visible; }
      .chapter--hero .reveal > span { transform: translateY(0); }
      .rail { display: none; }
      .void-field__deep { opacity: .5; }
      /* No looping video, no entrance animation — the poster still shows. */
      .dossier .dossier__bio p { opacity: 1; transform: none; }

      /* Deck: drop the 3D entirely, keep fade/scale and full functionality.
         Cards sit in a plain readable row and still open, flip-free. */
      .deck__stage { perspective: none; height: auto; }
      .deck__fan {
        position: static;
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 18px;
      }
      .pcard {
        position: static;
        width: var(--card-w);
        height: var(--card-h);
        margin: 0;
        transform: none !important;
        --s: 1;
      }
      .pcard__inner { transform: none !important; }
      .pcard__face--back { opacity: 1; transition: opacity 200ms ease; }
      .pcard.is-open .pcard__face--back { opacity: 0; }
      .pcard__face--front { transform: none; backface-visibility: visible; opacity: 0; transition: opacity 200ms ease; }
      .pcard.is-open .pcard__face--front { opacity: 1; }
      .deck__fan.has-open .pcard:not(.is-open) { filter: brightness(.4); }

      /* Codex: no 3D mask flight, no sticky pin. The plain chapter list is
         shown instead, which keeps all 33 chapters readable and functional. */
      .codex { height: auto !important; }
      .codex .codex__stage { display: none; }
      .codex .codex__source { display: block !important; }

      /* Closing + footer: everything present, nothing moving. The gate's
         travelling pulse is the one thing that must not loop. */
      .gate__signal::after { animation: none; opacity: .85; transform: translateY(90%); }
      .close .close__title,
      .close .close__cols,
      .close .close__act,
      .foot .foot__brand,
      .foot .foot__pay,
      .foot .foot__end { opacity: 1 !important; transform: none !important; }
    }
   .activity-signal {
  display: inline-flex;
  align-items: center;
  gap: 8px;

  margin-top: 18px;
  margin-bottom: 16px;

  color: var(--ice-mute);
  font-size: 12px;
  line-height: 1.4;
}

.activity-signal__dot {
  width: 6px;
  height: 6px;
  flex: 0 0 6px;
  border-radius: 50%;
  background: var(--blue-lift);

  animation: activityPulse 1.8s ease-in-out infinite;
}

.activity-signal__text {
  white-space: nowrap;
}

#activity-number {
  color: var(--blue-lift);
  display: inline-block;
  min-width: 2ch;
}

@keyframes activityPulse {
  0%,
  100% {
    opacity: 0.45;
    box-shadow: 0 0 0 0 rgba(61, 106, 255, 0);
  }

  50% {
    opacity: 1;
    box-shadow: 0 0 0 5px rgba(61, 106, 255, 0.12);
  }
}