/* ==========================================================================
   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;
    }

    /* Decorative CSS loops marked [data-loop] pause while off screen; a
       small script toggles .is-paused. A running loop, even out of sight,
       has the browser restyle the page on every frame. */
    [data-loop].is-paused,
    [data-loop].is-paused::before,
    [data-loop].is-paused::after { animation-play-state: paused !important; }

    /* ======================================================================
       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; }

    /* Everything printed on a card is sized from the card's own width, so the
       copy keeps its place inside the silver frame at every card size. With
       fixed pixel sizes a phone card (about 290px) got desktop-sized type:
       PERCEPTION ran past the frame, the number sat on the star under the J
       and the rule ran into the bottom star.

       Each factor is the desktop size divided by the desktop card width at
       1520px (22vw = 334.4px), so desktop renders exactly as before.
       --copy-type, --copy-title and --copy-gap tune a size class without
       touching the factors. */
    .pcard__label {
      margin: 0 0 calc(var(--card-w) * .041866 * var(--copy-gap, 1));
      font-family: var(--data);
      font-size: calc(var(--card-w) * .029904 * var(--copy-type, 1));
      letter-spacing: .3em;
      text-transform: uppercase;
      color: var(--card-ink);
    }

    .pcard__name {
      margin: 0 0 calc(var(--card-w) * .047847 * var(--copy-gap, 1));
      font-family: var(--display);
      font-size: calc(var(--card-w) * .077751 * var(--copy-title, 1));
      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 calc(var(--card-w) * .041866 * var(--copy-gap, 1));
      font-family: var(--body);
      font-size: calc(var(--card-w) * .037380 * var(--copy-type, 1));
      font-weight: 400;
      line-height: 1.48;
      color: rgba(255, 255, 255, .94);
    }

    .pcard__body {
      margin: 0;
      font-family: var(--body);
      font-size: calc(var(--card-w) * .032895 * var(--copy-type, 1));
      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: calc(var(--card-w) * .059809 * var(--copy-gap, 1));
      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 parts, turned with arrows. The mask is driven by
       CHAPTER STATE: it holds for every chapter inside a group and plays the
       real swap only when a step crosses into the next group.

       One screen tall. It used to pin the page for 33 wheel notches, which
       read as the site having ended; now the page scrolls straight past it
       and the chapters turn with ← → — the buttons, the keyboard or a swipe.
       ====================================================================== */
    .codex {
      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: relative;
      height: 100svh;
      /* A landscape phone is barely taller than the panel and the arrow bar
         together; below this they would collide. */
      min-height: 520px;
      overflow: clip;
      background: var(--void);
      /* Vertical drags stay the browser's, so the page scrolls over the stage
         as normal; only sideways movement reaches the script, as a swipe. */
      touch-action: pan-y;
    }

    /* A canvas frame sequence, not a <video>. Video can only be driven by
       play/pause or by seeking, so a swap interrupted by a second click would
       have to stop and restart — which is the cut. A frame sequence just
       eases toward its new target from wherever it is, forwards or back.
       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: count, cue, arrows ---------------------------------- */
    .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;
      min-width: 0;
      height: 1px;
      background: linear-gradient(90deg, var(--blue), rgba(0, 28, 120, 0));
    }

    /* Same cue language as the film section, turned to point at the arrows. */
    .codex__cue {
      display: inline-grid;
      grid-template-columns: auto 16px;
      align-items: center;
      gap: 9px;
      color: var(--blue-lift);
      white-space: nowrap;
      transition: opacity 500ms ease;
    }

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

    @keyframes cue-x {
      0%, 100% { transform: translateX(-6px) rotate(-45deg); opacity: .35; }
      50%      { transform: translateX(-2px) rotate(-45deg); opacity: 1; }
    }

    /* The wording follows the input: a mouse clicks, a thumb swipes. */
    .codex__cue-touch { display: none; }

    @media (hover: none), (pointer: coarse) {
      .codex__cue-fine  { display: none; }
      .codex__cue-touch { display: inline; }
    }

    /* Once the reader has turned a page, the prompt has done its job — and
       stops animating, since nobody can see it any more. */
    .codex.has-moved .codex__cue { opacity: 0; }
    .codex.has-moved .codex__cue::after { animation: none; }

    /* The arrows. Same material as the .cta: a hairline box in the lifted
       blue that fills with the brand blue on hover. Sized for a thumb. */
    .codex__nav {
      display: flex;
      flex: none;
      gap: 10px;
    }

    .codex__arrow {
      display: grid;
      place-items: center;
      width: 52px;
      height: 52px;
      padding: 0;
      border: 1px solid var(--blue-lift);
      border-radius: 0;
      background: rgba(0, 0, 0, .45);
      color: var(--blue-lift);
      font-family: var(--data);
      font-size: 18px;
      line-height: 1;
      cursor: pointer;
      transition: background 260ms ease, color 260ms ease, box-shadow 260ms ease, border-color 260ms ease;
    }

    /* Until the first step, the forward arrow already carries the fill, so
       it is the first thing the eye finds. Static — no loop to pay for. */
    .codex:not(.has-moved) .codex__arrow--next {
      background: rgba(0, 28, 120, .42);
      box-shadow: 0 0 26px rgba(0, 28, 120, .9);
    }

    .codex__arrow: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);
    }

    /* Hover only where there is a real pointer: a phone keeps the element it
       last tapped "hovered", which left the arrow glowing as if stuck. */
    @media (hover: hover) {
      .codex__arrow:hover:not(:disabled) {
        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);
      }
    }

    .codex__arrow:active:not(:disabled) { background: rgba(0, 28, 120, .8); }

    .codex__arrow:disabled {
      border-color: var(--hair);
      color: rgba(61, 106, 255, .4);
      cursor: default;
    }

    /* --- 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) .codex__stage { display: none; }

    /* ======================================================================
       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; }

      /* A narrow window on a mouse. Touch screens get their own height
         below, tuned to a swipe rather than a wheel notch. */
      .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;
        /* Nothing moves these on a phone — the flip belongs to .pcard__inner —
           so seven permanently promoted card layers were only GPU memory. */
        will-change: auto;
      }

      /* 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; }

      /* The card copy already shrinks with the card (see .pcard__label). On a
         phone that alone would leave the answer at ~9.5px, so the type is
         nudged up and the gaps between lines tightened instead. Measured on
         all seven cards at 390px and 360px: every block stays between the
         star under the J and the bottom-right star, and PERCEPTION ends at
         85% of the card width, inside the frame. Any larger and "Will" and
         "Control" wrap onto an extra line and run back into the stars. */
      .pcard__content { --copy-type: 1.08; --copy-gap: .5; }

      .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 --------------- */

      /* 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;
        /* Clears the arrow bar even on a short landscape screen. */
        padding-bottom: max(13vh, 84px);
      }

      .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; }
      .codex__nav { gap: 8px; }
      .codex__arrow { width: 46px; height: 46px; font-size: 17px; }
      /* Six hit areas across a phone width are too small to aim at. */
      .codex__parts { display: none; }

      /* --- 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; }
    }

    /* Touch screens: one swipe, one sentence. Measured on a 390x844 phone:
       at 200vh each sentence of the film got ~144px of finger travel, so an
       ordinary ~300px swipe landed two sentences on. At 350vh each gets
       ~360px, and swipes from ~225px to ~450px land exactly one sentence on.
       Keyed to the pointer, not the width, so a narrow desktop window keeps
       its wheel tuning and a touch tablet gets the swipe tuning. */
    @media (hover: none) and (pointer: coarse) {
      .sequence { height: 350vh; }
    }

    /* Below 360px the cue and both arrows no longer fit on one line; the
       arrows are the part that matters. */
    @media (max-width: 359px) {
      .codex__cue { display: none; }
    }

    /* ======================================================================
       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 mask flight. The plain chapter list is shown instead,
         which keeps all 33 chapters readable and functional. */
      .codex .codex__stage { display: none; }
      .codex .codex__source { display: block !important; }

      /* Closing + footer: everything present, nothing moving. */
      .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);
  }
}