  :root {
    --ink: #e8eef7;
    --dim: #8aa0bd;
    --accent: #6fd6ff;
    /* z-index scale — single source of truth so additions don't collide */
    --z-bg-canvas: 0;
    --z-bg-video: 1;
    --z-surface: 2;
    --z-content: 3;
    --z-grain: 8;
    --z-hint: 9;
    --z-hud: 10;
    --z-toolbar: 11;
    --z-menu: 12;
    --z-silence: 90;
    --z-intro: 100;
    --z-modal: 200;
    --z-toast: 300;
    --z-skip: 9999;
  }
  * { box-sizing: border-box; margin: 0; padding: 0; }

  /* a11y helpers */
  .visually-hidden {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
    border: 0;
  }
  .skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    background: #02060e;
    color: var(--accent);
    padding: 12px 18px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    text-decoration: none;
    border: 1px solid var(--accent);
    z-index: var(--z-skip);
  }
  .skip-link:focus {
    left: 16px;
    top: 16px;
  }
  /* keyboard focus is visible everywhere; mouse clicks don't show the ring */
  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 4px;
  }
  html, body {
    background: #000;
    color: var(--ink);
    font-family: "Iowan Old Style", "Palatino", Georgia, serif;
    font-size: 18px;
    line-height: 1.7;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
  }
  body { width: 100%; }

  /* fixed canvas background that reacts to scroll */
  #bg, #bg-static {
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100vh;
    display: block;
    z-index: var(--z-bg-canvas);
    pointer-events: none;
  }

  /* Total scroll length = depth journey */
  #journey {
    position: relative;
    width: 100%;
    /* very tall page — descent + reflection + transmit + ending hold */
    height: 3700vh;
    z-index: 2;
  }

  /* depth HUD */
  #hud {
    position: fixed;
    top: 0; right: 0;
    padding: 18px 22px;
    z-index: var(--z-hud);
    text-align: right;
    font-family: "SF Mono", "Menlo", monospace;
    font-size: 11px;
    letter-spacing: 0.18em;
    color: var(--dim);
    text-transform: uppercase;
    pointer-events: none;
    text-shadow: 0 0 10px rgba(0,0,0,0.8);
    /* hidden until Begin click — JS adds .hud-visible */
    opacity: 0;
    transition: opacity 1.6s ease;
  }
  #hud.hud-visible { opacity: 1; }
  #hud .depth {
    font-size: 28px;
    color: var(--ink);
    letter-spacing: 0.04em;
    font-family: "Iowan Old Style", Georgia, serif;
    text-transform: none;
  }
  #hud .label { opacity: 0.6; margin-bottom: 4px; }
  #hud .freq { margin-top: 14px; opacity: 0.85; }
  #hud .freq span { color: var(--accent); }

  /* HUD dim during reading (no scroll for 1.5s) */
  #hud.hud-visible.dim { opacity: 0.32; }

  /* chapter index next to depth meter */
  #hud .chapter-index {
    margin-top: 16px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.18em;
    color: var(--dim);
  }
  #hud .chapter-index span { color: var(--ink); }

  /* "still listening" cue — a small cyan dot in the HUD that pulses
     while the current chapter is still revealing its words. When the
     last word lands, JS removes body.chapter-revealing and the dot
     goes still and dim. Pure CSS animation, GPU compositor only —
     no JS frame work, no IntersectionObserver, no RAF. */
  #hud .hud-listen-row {
    margin-top: 14px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    opacity: 0.55;
  }
  #hud-listen {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
    /* static box-shadow — never animated. Animating box-shadow forces
       per-frame repaints which were tanking scroll perf. The pulse uses
       opacity only (GPU compositor, free). */
    box-shadow: 0 0 6px rgba(111,214,255,0.45);
    opacity: 0.28;
    transition: opacity 0.6s ease;
  }
  #hud .hud-listen-label {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.22em;
    color: var(--dim);
    text-transform: uppercase;
    opacity: 0.55;
    transition: opacity 0.6s ease;
  }
  body.chapter-revealing #hud-listen {
    animation: hudListenPulse 1.6s ease-in-out infinite;
    box-shadow: 0 0 12px rgba(111,214,255,0.8);
  }
  body.chapter-revealing #hud .hud-listen-label {
    opacity: 1;
    color: var(--accent);
    text-shadow: 0 0 8px rgba(111,214,255,0.4);
  }
  /* GO confirmation — fires once at the moment a chapter finishes
     revealing its words. Dot brightens to full opacity, label flips to
     accent color. Visual-only (no audio). JS removes the class after
     ~1.1s and the dot settles back to dim baseline. */
  body.chapter-just-revealed #hud-listen {
    opacity: 1;
    animation: none;
    transition: opacity 0.25s ease;
  }
  body.chapter-just-revealed #hud .hud-listen-label {
    opacity: 1;
    color: var(--accent);
  }
  /* opacity-only pulse — no box-shadow, no transform, no filter.
     GPU compositor handles this for free. */
  @keyframes hudListenPulse {
    0%, 100% { opacity: 0.4; }
    50%      { opacity: 1; }
  }
  @media (prefers-reduced-motion: reduce) {
    body.chapter-revealing #hud-listen {
      animation: none;
      opacity: 0.85;
    }
  }

  /* sources button — bottom right, quiet */
  #sources-btn {
    position: fixed;
    bottom: 18px;
    right: 22px;
    z-index: var(--z-hud);
    background: rgba(10,18,30,0.55);
    border: 1px solid rgba(111,214,255,0.3);
    color: var(--dim);
    padding: 8px 14px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.4s, color 0.4s;
  }
  #sources-btn:hover { background: rgba(111,214,255,0.12); color: var(--ink); }

  /* sources modal */
  /* Block layout + margin:auto on the inner — flex+align-items:center
     was clipping the top of the content when the list was taller than
     the viewport (flex centering pushes the top off-screen into a
     region scrolling can't reach). Block layout flows from top-down
     and scrolls cleanly. */
  #sources-modal {
    position: fixed;
    inset: 0;
    background: rgba(2,6,16,0.94);
    display: none;
    padding: 6vh 6vw 8vh;
    z-index: var(--z-modal);
    backdrop-filter: blur(10px);
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  #sources-modal.open { display: block; }
  #sources-modal .inner {
    max-width: 620px;
    margin: 0 auto;
    color: var(--ink);
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: 14px;
    line-height: 1.6;
  }
  #sources-modal h3 {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--accent);
    margin: 0 0 22px;
  }
  #sources-modal ul { list-style: none; padding: 0; margin: 0; }
  #sources-modal li {
    padding: 12px 0;
    border-bottom: 1px solid rgba(111,214,255,0.12);
  }
  #sources-modal li:last-child { border-bottom: 0; }
  #sources-modal b { color: var(--accent); font-weight: normal; font-family: "SF Mono", Menlo, monospace; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; display: block; margin-bottom: 4px; }
  #sources-modal a { color: var(--accent); text-decoration: none; border-bottom: 1px dotted rgba(111,214,255,0.4); }
  #sources-modal a:hover { color: var(--ink); border-bottom-color: var(--ink); }
  #sources-modal code { font-family: "SF Mono", Menlo, monospace; font-size: 12px; color: var(--dim); }
  #sources-modal .close {
    margin-top: 28px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--accent);
    cursor: pointer;
  }

  /* post-credits CTA — fades in after the ending sequence */
  .post-credits {
    margin: 60px auto 0;
    max-width: 540px;
    text-align: center;
    opacity: 0;
    transition: opacity 4s ease;
  }
  .post-credits.show { opacity: 1; }
  .post-credits .line {
    margin: 16px 0;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
    opacity: 0;
    transition: opacity 2.5s ease;
  }
  /* Tightened from 1/3.5/6/8.5 — credits were taking too long. */
  .post-credits.show .line:nth-child(1) { transition-delay: 0.3s; opacity: 1; }
  .post-credits.show .line:nth-child(2) { transition-delay: 1.0s; opacity: 1; }
  .post-credits.show .line:nth-child(3) { transition-delay: 1.7s; opacity: 1; }
  .post-credits.show .line:nth-child(4) { transition-delay: 2.4s; opacity: 1; }
  .post-credits a {
    color: var(--dim);
    text-decoration: none;
    border-bottom: 1px dotted rgba(111,214,255,0.25);
    transition: color 0.4s, border-color 0.4s;
  }
  .post-credits a:hover {
    color: var(--accent);
    border-bottom-color: var(--accent);
  }

  /* ----------------------------------------------------------
     CREATOR BYLINE — author attribution at the very end.
     Fades in after the post-credits links (which are themselves
     staggered up to 8.5s after .post-credits.show is added).
     The triggering .show class lives on the previous sibling
     (.post-credits), so a sibling selector + delay works.
     ---------------------------------------------------------- */
  .creator-byline {
    margin: 80px auto 32px;
    max-width: 540px;
    text-align: center;
    opacity: 0;
    transition: opacity 2.5s ease;
  }
  .post-credits.show ~ .creator-byline {
    opacity: 1;
    transition-delay: 4s;
  }
  .creator-byline .creator-line {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10.5px;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 18px;
  }
  .creator-byline .creator-name {
    font-family: "Iowan Old Style", "Palatino", Georgia, serif;
    font-style: italic;
    font-size: clamp(28px, 4vw, 42px);
    color: var(--ink);
    line-height: 1.15;
    letter-spacing: 0.02em;
  }
  .creator-byline .creator-year {
    margin-top: 16px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.28em;
    color: var(--dim);
  }
  /* The "— end —" label and the tech credits at the very bottom of c11
     are hidden until the wave animation in the final-call canvas
     completes. fireFinalCall (in ui.js) adds .reveal to each at the
     right moment, then the credits chain plays. */
  #c11 .end-label,
  #c11 .tech-credits {
    opacity: 0;
    transition: opacity 1.4s ease;
  }
  #c11 .end-label.reveal { opacity: 1; }
  #c11 .tech-credits {
    margin-top: 24px;
  }
  #c11 .tech-credits.reveal { opacity: 0.55; }

  /* Final invite — last thing on the page, points the reader past the
     credits into the secret ending below. Revealed by revealEndingCredits
     after the tech credits have landed. */
  #c11 .descend-invite {
    margin-top: 44px;
    font-family: "Iowan Old Style", "Palatino", Georgia, serif;
    font-style: italic;
    font-size: clamp(15px, 1.9vw, 20px);
    color: rgba(111, 214, 255, 0.85);
    text-align: center;
    letter-spacing: 0.02em;
    opacity: 0;
    transition: opacity 2.2s ease;
  }
  #c11 .descend-invite.reveal { opacity: 1; }
  #c11 .descend-invite .descend-arrow {
    display: block;
    margin-top: 18px;
    font-size: 18px;
    font-style: normal;
    color: rgba(111, 214, 255, 0.55);
    animation: descendInviteArrow 3s ease-in-out infinite;
  }
  @keyframes descendInviteArrow {
    0%, 100% { transform: translateY(0);  opacity: 0.5; }
    50%      { transform: translateY(9px); opacity: 0.95; }
  }
  @media (prefers-reduced-motion: reduce) {
    #c11 .descend-invite .descend-arrow { animation: none; }
  }

  /* transmit "received nothing" overlay during the silence */
  .send-call .received-nothing {
    margin-top: 22px;
    height: 18px;
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: 14px;
    color: var(--dim);
    opacity: 0;
    transition: opacity 1.5s ease;
  }
  .send-call .received-nothing.show { opacity: 0.85; }

  /* post-transmit text — fixed overlay that appears center-screen after
     the reader's call goes unanswered. Position:fixed so it's visible
     regardless of scroll position within c9c. */
  .post-transmit {
    position: fixed;
    inset: 0;
    background: rgba(1,3,8,0.98);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    z-index: var(--z-silence);
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 8vw;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.5s cubic-bezier(.2,.6,.2,1);
  }
  .post-transmit.show { opacity: 1; pointer-events: auto; }
  .post-transmit .pt-phrase {
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-weight: 400;
    font-size: clamp(34px, 7.5vw, 100px);
    line-height: 1.1;
    color: var(--ink);
    letter-spacing: 0.005em;
    max-width: 1100px;
    opacity: 0;
    transform: translateY(12px);
    transition:
      opacity 1.8s cubic-bezier(.2,.6,.2,1),
      transform 1.8s cubic-bezier(.2,.6,.2,1);
    text-shadow: 0 2px 40px rgba(0,0,0,0.9);
    position: absolute;
  }
  .post-transmit .pt-phrase.visible {
    opacity: 1;
    transform: translateY(0);
  }
  .post-transmit-hint {
    margin-top: 60px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--dim);
    opacity: 0;
    visibility: hidden;
    transition: opacity 1.8s ease;
  }
  .post-transmit-hint.show { visibility: visible; opacity: 0.7; }
  /* bob on the arrow only — applying it to the whole hint made the
     keyframed opacity fight the 1.8s fade-in and pop the text in. */
  .post-transmit-hint .arrow {
    display: inline-block;
    margin-left: 8px;
    animation: bob 2.4s infinite ease-in-out;
  }

     from the video underneath every frame), no box-shadow animation
     (compositor death). Solid dark background + cyan border + cyan text
     is plenty visible against the videos. */
  #sound {
    position: fixed;
    bottom: 32px; left: 50%; transform: translateX(-50%);
    /* must sit above #intro (z-index: 100) so the reader can toggle
       sound on/off while the title screen is up */
    z-index: 101;
    background: rgba(2,6,16,0.95);
    border: 2px solid rgba(111,214,255,0.7);
    color: var(--accent);
    padding: 12px 24px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 12px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.4s, border-color 0.4s, opacity 0.4s, transform 0.4s;
    /* visible pulse while sound is off — opacity only (GPU compositor). */
    animation: soundPulse 1.8s ease-in-out infinite;
  }
  @keyframes soundPulse {
    0%, 100% { opacity: 0.5; }
    50%      { opacity: 1; }
  }
  #sound:hover { background: rgba(111,214,255,0.18); opacity: 1; }
  #sound.on {
    /* once sound is on, shrink and move to bottom-left corner */
    bottom: 22px; left: 22px; transform: none;
    padding: 9px 14px;
    font-size: 10px;
    letter-spacing: 0.18em;
    border: 1px solid rgba(111,214,255,0.9);
    color: var(--accent);
    animation: none;
    opacity: 1;
  }
  #music-toggle {
    position: fixed;
    bottom: 62px; right: 22px;  /* stacked above #sources-btn */
    z-index: 101;
    background: rgba(2,6,16,0.92);
    border: 1px solid rgba(111,214,255,0.4);
    color: var(--dim);
    padding: 9px 14px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.6s, background 0.4s, border-color 0.4s;
    pointer-events: none;
  }
  /* Hidden during the post-transmit silence overlay — the reader is
     meant to sit in total silence; a music toggle would break that. */
  body.post-transmit-active #music-toggle { display: none; }
  #music-toggle.visible {
    opacity: 0.85;
    pointer-events: auto;
  }
  #music-toggle:hover { background: rgba(111,214,255,0.18); }
  #music-toggle.off {
    color: var(--dim);
    border-color: rgba(111,214,255,0.2);
  }

  /* manual stop button — sits just above the sound-caption / progress
     bar at bottom-center so the reader can stop the clip right where
     they're watching it play. Only visible while audio is playing. */
  #stop-audio-btn {
    position: fixed;
    top: auto;
    bottom: 108px;
    left: 50%;
    transform: translateX(-50%);
    width: 40px;
    height: 34px;
    z-index: var(--z-toolbar);
    width: 30px;
    background: rgba(18,8,10,0.85);
    border: 1.5px solid rgba(255,140,140,0.85);
    color: rgba(255,210,210,0.95);
    font-family: "SF Mono", Menlo, monospace;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s ease, background 0.3s, border-color 0.3s, box-shadow 0.3s;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 10px rgba(0,0,0,0.6);
    z-index: calc(var(--z-toolbar) + 1);
  }
  #stop-audio-btn.show {
    opacity: 1;
    pointer-events: auto;
  }
  #stop-audio-btn:hover {
    background: rgba(255,140,140,0.18);
    color: #fff;
  }

  /* scroll hint — starts at opacity 0, JS fades it in (via the transition
     below) about 3 seconds after page load, and JS fades it out on the
     first meaningful scroll. Both transitions are driven by inline styles
     so they can override each other; no CSS animation is used because
     CSS animations beat inline styles, which would prevent the fade-out. */
  #hint {
    position: fixed;
    top: 50%; left: 0; right: 0;
    transform: translateY(-50%);
    text-align: center;
    z-index: var(--z-hint);
    font-family: "SF Mono", Menlo, monospace;
    font-size: 13px;
    letter-spacing: 0.36em;
    text-transform: uppercase;
    color: var(--ink);
    opacity: 0;
    transition: opacity 1.4s ease;
    pointer-events: none;
  }
  /* After the first appearance, the hint moves to the bottom-left so it
     balances the depth HUD (top-right) without stealing center stage from
     the chapter text. The initial show — and the post-transmit hint —
     stay vertically centered. */
  #hint.left {
    top: auto;
    bottom: 32px;
    left: 22px; right: auto;
    transform: none;
    text-align: left;
    padding-right: 0;
    max-width: 260px;
    font-size: 11px;
    letter-spacing: 0.32em;
  }
  /* Top-center variant — used while fish-37 plays, so the prompt sits
     above the video overlay instead of behind it. */
  #hint.top-center {
    top: 80px;
    bottom: auto;
    left: 0; right: 0;
    transform: none;
    text-align: center;
    font-size: 12px;
    letter-spacing: 0.34em;
  }
  #hint .arrow {
    display: block;
    margin-top: 10px;
    font-size: 14px;
    animation: bob 2.4s infinite ease-in-out;
  }
  #hint.left .arrow {
    text-align: left;
  }
  @keyframes bob {
    0%,100% { transform: translateY(0); opacity: 0.5; }
    50%     { transform: translateY(8px); opacity: 1; }
  }

  /* story chapters */
  .chapter {
    position: absolute;
    left: 0; right: 0;
    padding: 0 8vw;
    max-width: 720px;
    margin: 0 auto;
    z-index: var(--z-content);
  }
  .chapter h2 {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 16px;
    opacity: 0.85;
  }
  .chapter p {
    margin-bottom: 1.1em;
    text-shadow: 0 1px 18px rgba(0,0,0,0.85);
  }
  .chapter p.lead { font-size: 1.15em; }
  .chapter p.quiet { color: var(--dim); font-style: italic; }
  .chapter .sig {
    display: inline-block;
    color: var(--accent);
    border: 1px solid rgba(111,214,255,0.35);
    padding: 2px 8px;
    margin: 2px 0;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 0.85em;
    letter-spacing: 0.1em;
  }

  /* place each chapter at a depth */
  #c0  { top:  32vh; }
  /* every chapter top is calibrated to give the previous chapter
     enough height for ALL its content (prose + cyan media boxes +
     character cards + graphics) before the next one begins. */
  #c1  { top: 140vh; }
  #c2  { top: 280vh; }
  #c3  { top: 460vh; }
  #c4  { top: 660vh; }
  #c5  { top: 900vh; }   /* c4 has the year counter */
  #c6  { top:1240vh; }   /* c5 has people + specimen + whale chart */
  #c7  { top:1580vh; }   /* c6 has spectrogram + listening station */
  #c8  { top:1840vh; }   /* c7 has the full-bleed pullquote */
  #c9  { top:2080vh; }   /* c8 has the migration map */
  #c9b { top:2300vh; }   /* c9 has letters stage */
  #c9c { top:2520vh; }   /* c9b has artifacts stage */
  #c10 { top:2760vh; }   /* c9c has transmit panel */
  #c11 { top:3120vh; left: 0; right: 0; text-align: center; max-width: none; padding: 0 6vw; }
  /* c10 has lead-line + drift graphic + searchers card — needs 360vh */

  /* fade-in via JS-controlled class */
  .chapter { opacity: 0; transform: translateY(20px); transition: opacity 1.2s ease, transform 1.2s ease; }
  .chapter.visible { opacity: 1; transform: translateY(0); }

  /* ----------------------------------------------------------
     SYNCHRONIZED REVEAL — interactive elements (cues, sig boxes,
     media frames, listening station, pull quotes) MUST hold until
     the kinetic prose around them has had a chance to land. Delays
     are deliberately long because long paragraphs take 6—8 seconds
     for their last word to fade in.
     ---------------------------------------------------------- */
  /* The .sig box (chapter 3's cyan "52 Hz" reveal) still uses the
     parent-opacity reveal — it's a SINGLE word treated as a box, not
     a kineticized container, so the parent fade is correct.
     The .cue buttons, however, contain words that are kineticized into
     .bw spans by chapters.js. If the parent .cue had opacity:0, the inner
     words would be hidden too — and visibly delayed past their natural
     reveal positions, breaking the left-to-right reading order.
     So .cue stays at opacity:1; instead, its dashed underline starts
     transparent and fades in at a per-button delay set by JS, so the
     line appears right after its own words land. */
  .chapter .sig {
    opacity: 0;
    /* delay set dynamically by JS based on inner .bw word index */
    transition: opacity 1.4s ease, color 0.3s, border-color 0.3s, text-shadow 0.3s, background 0.3s;
  }
  .chapter.visible .sig {
    opacity: 1;
  }
  .chapter .cue {
    border-bottom-color: transparent;
    transition: color 0.3s ease,
                border-color 1.2s ease var(--cue-delay, 12s),
                text-shadow 0.3s ease,
                background 0.3s ease;
  }
  .chapter.visible .cue {
    border-bottom-color: rgba(111,214,255,0.55);
  }
  .chapter .cue:hover,
  .chapter .cue:focus-visible {
    border-bottom-color: var(--accent);
    transition-delay: 0s;
  }
  /* c3/c4/c7 progressive reveals — scroll-position driven.
     Each held paragraph stays hidden until the reader scrolls it into
     view. Then its words kinetize with a fresh stagger from that
     moment. This replaces the old hardcoded-time gates which broke
     at different scroll speeds and left readers staring at blank space.
     The .revealed class is added by JS via IntersectionObserver when
     the paragraph enters the viewport (see wireScrollReveals). */
  #c3 .hold-after-sig .bw,
  #c3 .hold-after-sig-extra .bw,
  #c4 .hold-after-counter .bw,
  #c7 .hold-after-pullquote .bw {
    opacity: 0 !important;
    transition: opacity 0.7s ease !important;
  }
  #c3 .hold-after-sig.revealed .bw,
  #c3 .hold-after-sig-extra.revealed .bw,
  #c7 .hold-after-pullquote.revealed .bw {
    opacity: 1 !important;
    transition-delay: calc(var(--i, 0) * 0.10s + 0.15s) !important;
  }
  /* c4 post-graph line: the year counter is the payoff; this paragraph
     is a summary that should LAND, not unfurl. Tight stagger so all
     words arrive within ~0.6s of the reveal trigger. */
  #c4 .hold-after-counter.revealed .bw {
    opacity: 1 !important;
    transition-delay: calc(var(--i, 0) * 0.025s + 0.05s) !important;
  }
  /* Synchronized reveal — the chapter-scoped selectors below have
     higher specificity (0,2,0) than the component rules (0,1,0),
     so they cascade naturally without !important. */
  .chapter .media,
  .chapter .pullquote,
  .chapter .specimen-card,
  .chapter .whale-chart,
  .chapter .sequence-cue,
  .chapter .year-counter,
  .chapter .drift-graphic,
  .chapter .lead-line,
  .chapter .people {
    opacity: 0;
    transform: translateY(14px);
    border-color: transparent;
    background: transparent;
    box-shadow: none;
    /* no delay — JS adds .in-view when the element reaches the viewport,
       same approach as Snow Fall's checkCurrentView */
    transition: opacity 0.8s ease, transform 0.8s ease,
                border-color 0.6s ease, background 0.6s ease,
                box-shadow 0.6s ease;
  }
  .chapter .media.in-view,
  .chapter .pullquote.in-view,
  .chapter .specimen-card.in-view,
  .chapter .whale-chart.in-view,
  .chapter .sequence-cue.in-view,
  .chapter .year-counter.in-view,
  .chapter .drift-graphic.in-view,
  .chapter .lead-line.in-view,
  .chapter .people.in-view {
    opacity: 1;
    transform: translateY(0);
    border-color: rgba(111,214,255,0.28);
    background: rgba(4,10,22,0.6);
  }
  /* No per-chapter delay overrides — boxes use scroll-position .in-view */
  /* the listening station holds longest — it's the FORMAL panel
     and should appear after the inline cues have already had a
     chance to draw the eye */
  .chapter .listening-station {
    opacity: 0;
    transform: translateY(18px);
    border-color: transparent;
    background: transparent;
    transition: opacity 2s ease, transform 2s ease,
                border-color 1.6s ease, background 1.6s ease;
  }
  .chapter .listening-station.in-view {
    opacity: 1;
    transform: translateY(0);
    border-color: rgba(111,214,255,0.28);
    background: rgba(4,10,22,0.62);
  }

  /* lyrical chapters reveal even more slowly */
  .chapter[data-pace="lyrical"] .cue,
  .chapter[data-pace="lyrical"] .sig {
    transition-delay: 10s;
  }
  .chapter[data-pace="lyrical"] .media,
  .chapter[data-pace="lyrical"] .pullquote {
    transition-delay: 8s;
  }

  /* author byline on the intro screen — sits below the begin button,
     small and unobtrusive, the way print bylines belong under headlines */
  #intro .byline {
    margin-top: 42px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--dim);
    opacity: 0;
    animation: introFade 1.6s 5s forwards;
  }
  @media (max-width: 720px) {
    #intro .byline { font-size: 8.5px; margin-top: 22px; }
  }

  /* big finale — semantically an h2, visually a display headline */
  #c11 h2.finale-headline {
    font-family: "Iowan Old Style", "Palatino", Georgia, serif;
    font-size: clamp(28px, 5vw, 56px);
    font-weight: 400;
    line-height: 1.25;
    margin-bottom: 30px;
    text-shadow: 0 2px 30px rgba(0,0,0,0.9);
    letter-spacing: 0.005em;
    color: var(--ink);
    text-transform: none;
    /* override .chapter h2 styles which would otherwise mono-uppercase this */
  }
  #c11 .small {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
    margin-top: 24px;
  }

  /* surface light gradient at top */
  #surface {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 30vh;
    pointer-events: none;
    z-index: var(--z-surface);
    background: radial-gradient(ellipse at 50% -20%, rgba(180,220,255,0.55), rgba(80,140,200,0.18) 40%, transparent 80%);
    transition: opacity 1.5s;
  }

  /* film grain overlay — subtle texture so canvas reads as footage */
  #grain {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: var(--z-grain);
    opacity: 0.08;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='2.4' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 1 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  }

  /* embedded media boxes inside chapters */
  .media {
    margin: 28px 0 8px;
    border: 1px solid rgba(111,214,255,0.25);
    background: rgba(4,10,22,0.55);
    padding: 14px 16px;
  }
  .media .cap {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 10px;
    display: flex;
    justify-content: space-between;
  }
  .media canvas { display: block; width: 100%; height: auto; }

  /* listening station — user-triggered hydrophone sources */
  .listening-station {
    margin: 22px 0 8px;
    border: 1px solid rgba(111,214,255,0.28);
    background: rgba(4,10,22,0.62);
    padding: 16px 16px 14px;
  }
  .listening-station .ls-cap {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 14px;
    text-align: center;
    display: block;
  }
  .listening-station .ls-prompt {
    display: block;
    margin: 22px auto 0;
    width: max-content;
  }
  .listening-station .ls-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px 28px;
    margin-top: 6px;
  }
  @media (max-width: 720px) {
    .listening-station .ls-grid { grid-template-columns: 1fr 1fr; gap: 10px; }
    .listening-station .ls-prompt { margin-top: 16px; }
  }
  .listening-station button {
    background: rgba(10,18,30,0.65);
    border: 1px solid rgba(111,214,255,0.35);
    color: var(--ink);
    padding: 12px 11px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.3s, border-color 0.3s, color 0.3s, transform 0.2s;
    text-align: left;
    line-height: 1.35;
  }
  .listening-station button:hover {
    background: rgba(111,214,255,0.12);
    border-color: rgba(111,214,255,0.7);
  }
  .listening-station button.playing {
    background: rgba(111,214,255,0.20);
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: 0 0 24px rgba(111,214,255,0.18) inset;
  }
  .listening-station button .hz {
    display: block;
    font-size: 8.5px;
    opacity: 0.55;
    margin-top: 4px;
    letter-spacing: 0.16em;
  }
  @media (max-width: 720px) {
    .listening-station button { font-size: 9.5px; padding: 11px 9px; }
  }

  /* listening station sub-header */
  .ls-real-head {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 10px;
  }

  /* inline trigger words inside prose — Snow Fall style.
     The reader can click the actual nouns ("tectonic plates", "cargo
     ships") to hear them. Subtle, but unmistakably interactive. */
  .cue {
    /* button reset so the inline <button> reads as inline prose */
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: 0;
    border-bottom: 1px dashed rgba(111,214,255,0.55);
    padding: 0 0 1px;
    margin: 0;
    font: inherit;
    line-height: inherit;
    text-align: inherit;
    display: inline;
    color: var(--accent);
    cursor: pointer;
    transition: color 0.3s ease, border-color 0.3s ease, text-shadow 0.3s ease, background 0.3s ease;
  }
  .cue::before {
    content: "♪ ";
    font-size: 0.72em;
    opacity: 0;
    margin-right: 1px;
    transition: opacity 1.2s ease var(--cue-delay, 12s);
  }
  .chapter.visible .cue::before {
    opacity: 0.7;
  }
  .cue:hover {
    color: #ffffff;
    border-bottom-color: var(--accent);
    text-shadow: 0 0 14px rgba(111,214,255,0.55);
  }
  .cue.playing {
    color: #ffffff;
    background: rgba(111,214,255,0.10);
    text-shadow: 0 0 18px rgba(111,214,255,0.85);
    border-bottom-color: var(--accent);
  }

  /* small "tap to hear" badge inside the listening-station header */
  .ls-prompt {
    display: inline-block;
    margin-left: 10px;
    padding: 2px 8px;
    background: rgba(111,214,255,0.18);
    border: 1px solid rgba(111,214,255,0.55);
    color: var(--accent);
    font-size: 8.5px;
    letter-spacing: 0.2em;
    border-radius: 1px;
  }

  /* one-time breath animation: when the listening station first scrolls
     into view, it pulses twice so the reader's eye lands on it. */
  @keyframes lsBreathe {
    0%, 100% { opacity: 0; }
    50%      { opacity: 1; }
  }
  .listening-station.attention {
    position: relative;
  }
  .listening-station.attention::after {
    content: '';
    position: absolute;
    inset: -1px;
    box-shadow: 0 0 36px 0 rgba(111,214,255,0.22);
    pointer-events: none;
    animation: lsBreathe 2.6s ease-in-out 2;
  }

  /* ----------------------------------------------------------
     SEQUENCE PLAYER — c2: "play all three back to back"
     ---------------------------------------------------------- */
  .sequence-cue {
    margin: 28px 0 8px;
    padding: 18px;
    border: 1px solid rgba(111,214,255,0.28);
    background: rgba(4,10,22,0.55);
    text-align: center;
  }
  .sequence-cue button {
    background: rgba(10,18,30,0.7);
    border: 1px solid rgba(111,214,255,0.5);
    color: var(--accent);
    padding: 14px 22px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    cursor: pointer;
    transition: background 0.3s, border-color 0.3s, color 0.3s, letter-spacing 0.3s;
  }
  .sequence-cue button:hover {
    background: rgba(111,214,255,0.15);
    color: #fff;
    letter-spacing: 0.26em;
  }
  .sequence-cue button.playing {
    background: rgba(111,214,255,0.22);
    color: #fff;
    border-color: var(--accent);
    box-shadow: 0 0 30px rgba(111,214,255,0.2) inset;
  }
  .sequence-cue .seq-caption {
    margin-top: 12px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--dim);
    transition: color 0.4s;
    min-height: 14px;
  }
  .sequence-cue .seq-caption.active { color: var(--accent); }

  /* ----------------------------------------------------------
     YEAR COUNTER — c4: real call-detection counts 1989 → 1992
     A growing horizontal bar per year showing how many days the
     call was detected. The bar lengths are scaled to the 1992
     value (55 days). Stagger animation makes the years arrive
     one at a time so the patience of the science is felt.
     ---------------------------------------------------------- */
  .year-counter {
    margin: 26px 0 14px;
    padding: 20px 24px;
    border: 1px solid rgba(111,214,255,0.22);
    background: rgba(4,10,22,0.6);
  }
  .year-counter .yc-cap {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 16px;
  }
  .year-counter .yc-cap.quiet {
    color: var(--dim);
    margin: 18px 0 0;
    text-align: center;
    font-style: normal;
  }
  .year-counter .yc-row {
    display: grid;
    grid-template-columns: 56px 1fr 130px;
    align-items: center;
    gap: 14px;
    height: 28px;
    margin-bottom: 6px;
    opacity: 0;
    transform: translateX(-12px);
    transition: opacity 0.7s ease, transform 0.7s ease;
  }
  .year-counter .yc-row.lit { opacity: 1; transform: none; }
  .year-counter .yc-year {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 14px;
    letter-spacing: 0.08em;
    color: var(--ink);
  }
  .year-counter .yc-bar {
    height: 6px;
    background: rgba(111,214,255,0.08);
    position: relative;
    overflow: hidden;
  }
  .year-counter .yc-bar-fill {
    position: absolute;
    top: 0; left: 0; bottom: 0;
    width: 0;
    background: linear-gradient(90deg, rgba(111,214,255,0.4), rgba(111,214,255,0.85));
    transition: width 1s cubic-bezier(.2,.6,.2,1);
    box-shadow: 0 0 12px rgba(111,214,255,0.4);
  }
  .year-counter .yc-num {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--dim);
    text-align: right;
  }
  .year-counter .yc-row.lit-final.lit .yc-year { color: var(--accent); }
  .year-counter .yc-row.lit-final.lit .yc-num  { color: var(--accent); }
  .year-counter .yc-row.lit-final.lit .yc-bar-fill {
    background: linear-gradient(90deg, rgba(111,214,255,0.7), #ffffff);
    box-shadow: 0 0 18px rgba(111,214,255,0.7);
  }

  /* ----------------------------------------------------------
     SPECIMEN CARD — c5: the whale's only "portrait"
     ---------------------------------------------------------- */
  .specimen-card {
    margin: 30px 0 8px;
    padding: 22px 26px 20px;
    border: 1px solid rgba(111,214,255,0.4);
    background: rgba(4,10,22,0.7);
  }
  .specimen-card .sc-head {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--accent);
    padding-bottom: 12px;
    border-bottom: 1px solid rgba(111,214,255,0.25);
    margin-bottom: 14px;
  }
  .specimen-card .sc-rows {
    display: grid;
    gap: 7px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    color: var(--ink);
  }
  .specimen-card .sc-row { display: flex; gap: 14px; }
  .specimen-card .sc-row .k {
    color: var(--dim);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    font-size: 9px;
    width: 78px;
    flex-shrink: 0;
    padding-top: 2px;
  }
  .specimen-card .sc-row .v { color: var(--ink); flex: 1; }
  .specimen-card .sc-play {
    margin-top: 18px;
    background: transparent;
    border: 1px solid rgba(111,214,255,0.5);
    color: var(--accent);
    padding: 11px 18px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    cursor: pointer;
    width: 100%;
    transition: background 0.3s, color 0.3s, letter-spacing 0.3s;
  }
  .specimen-card .sc-play:hover { background: rgba(111,214,255,0.14); color: #fff; letter-spacing: 0.24em; }
  .specimen-card .sc-play.playing {
    background: rgba(111,214,255,0.22);
    color: #fff;
    box-shadow: 0 0 24px rgba(111,214,255,0.18) inset;
  }
  .specimen-card .sc-foot {
    margin-top: 14px;
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: 12px;
    color: var(--dim);
    text-align: center;
  }

  /* ----------------------------------------------------------
     WHALE SIZE CHART — c5 sidebar to the specimen card
     ---------------------------------------------------------- */
  .whale-chart {
    margin: 24px 0 8px;
    padding: 18px 22px 16px;
    border: 1px solid rgba(111,214,255,0.28);
    background: rgba(4,10,22,0.62);
  }
  .whale-chart .wc-head {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 14px;
    padding-bottom: 10px;
    border-bottom: 1px solid rgba(111,214,255,0.2);
  }
  .whale-chart .wc-svg {
    display: block;
    width: 100%;
    height: auto;
    max-width: 100%;
  }
  .whale-chart .wc-foot {
    margin-top: 12px;
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: 12px;
    line-height: 1.5;
    color: var(--dim);
  }

  /* ----------------------------------------------------------
     PEOPLE — character cards (c5: researchers, c10: searchers)
     ---------------------------------------------------------- */
  .people {
    margin: 28px 0 8px;
    padding: 18px 22px 16px;
    border: 1px solid rgba(111,214,255,0.28);
    background: rgba(4,10,22,0.6);
  }
  .people-head {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 14px;
    padding-bottom: 10px;
    border-bottom: 1px solid rgba(111,214,255,0.2);
  }
  .people-grid {
    display: grid;
    gap: 16px;
  }
  figure.person-card {
    display: grid;
    grid-template-columns: 60px 1fr;
    gap: 14px;
    align-items: start;
    margin: 0;
  }
  .person-avatar {
    width: 60px;
    height: 60px;
    border: 1px solid rgba(111,214,255,0.55);
    background: rgba(10,18,30,0.7);
    color: var(--accent);
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 18px;
    letter-spacing: 0.06em;
    border-radius: 1px;
    overflow: hidden;
    flex-shrink: 0;
  }
  .person-avatar.person-avatar-photo {
    padding: 0;
    background: #000;
  }
  .person-avatar.person-avatar-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 30%;
    display: block;
  }
  img.photo-zoomable {
    cursor: zoom-in;
    transition: opacity 0.3s ease;
  }
  img.photo-zoomable:hover { opacity: 0.85; }

  /* photo lightbox */
  #photo-lightbox {
    position: fixed;
    inset: 0;
    z-index: var(--z-modal);
    background: rgba(1,4,10,0.95);
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s ease;
  }
  #photo-lightbox.open {
    opacity: 1;
    pointer-events: auto;
  }
  #photo-lightbox img {
    max-width: 85vw;
    max-height: 85vh;
    object-fit: contain;
    border-radius: 4px;
    box-shadow: 0 4px 60px rgba(0,0,0,0.8);
  }
  #lightbox-close {
    position: absolute;
    top: 24px; right: 28px;
    background: none;
    border: 1px solid rgba(255,255,255,0.3);
    color: var(--ink);
    font-size: 28px;
    width: 44px; height: 44px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: border-color 0.3s;
  }
  #lightbox-caption {
    position: absolute;
    bottom: 28px;
    left: 0; right: 0;
    text-align: center;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.18em;
    color: var(--dim);
    opacity: 0;
    transition: opacity 0.5s ease;
  }
  #photo-lightbox.open #lightbox-caption { opacity: 0.7; }
  /* low-res photos get a slight smoothing so they don't look pixel-harsh */
  img.photo-lowres { image-rendering: auto; }
  #photo-lightbox img.lowres-active {
    image-rendering: auto;
    filter: contrast(1.05);
    max-width: 500px;
  }
  #lightbox-close:hover { border-color: var(--ink);
    filter: grayscale(0.15) contrast(1.05);
  }
  .person-info {
    color: var(--ink);
  }
  .person-name {
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: 16px;
    font-weight: normal;
    color: var(--ink);
    margin-bottom: 3px;
  }
  .person-role {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 8px;
  }
  .person-bio {
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: 13px;
    line-height: 1.55;
    color: rgba(220,235,255,0.85);
  }
  @media (max-width: 720px) {
    figure.person-card { grid-template-columns: 48px 1fr; }
    .person-avatar { width: 48px; height: 48px; font-size: 14px; }
    .person-name { font-size: 14px; }
    .person-bio { font-size: 12px; }
  }

  /* ----------------------------------------------------------
     FREQUENCY DRIFT — c10 small graphic
     ---------------------------------------------------------- */
  .drift-graphic {
    margin: 26px 0 14px;
    padding: 18px 22px;
    border: 1px solid rgba(111,214,255,0.22);
    background: rgba(4,10,22,0.55);
  }
  .drift-graphic .dg-cap {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
    margin-bottom: 12px;
  }
  .drift-graphic .dg-cap.quiet { color: var(--dim); margin: 12px 0 0; }
  #drift-canvas { display: block; width: 100%; height: auto; max-width: 100%; aspect-ratio: 640 / 120; }

  .lead-line {
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: 1.3em;
    line-height: 1.4;
    color: var(--ink);
    text-align: center;
    margin: 38px auto 18px;
    max-width: 520px;
    padding: 22px 0;
    border-top: 1px solid rgba(111,214,255,0.18);
    border-bottom: 1px solid rgba(111,214,255,0.18);
  }

  /* the single-word "anomaly" closer for c7 — sits alone, huge */
  .anomaly-word {
    text-align: center;
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: clamp(48px, 9vw, 110px);
    font-weight: 400;
    color: var(--accent);
    letter-spacing: 0.04em;
    line-height: 1;
    margin: 36px 0 18px;
    text-shadow: 0 0 40px rgba(111,214,255,0.25);
  }

  /* c7 closer — the definitional line that follows the theory branches.
     Serif + wide letter-spacing for a cold, scientific weight. Same
     accent blue as ANOMALY so the two moments feel linked. Restrained
     glow — this is bioluminescence through dark water, not a banner. */
  .theory-closer {
    text-align: center;
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: clamp(22px, 3.2vw, 38px);
    font-weight: 400;
    color: var(--accent);
    letter-spacing: 0.05em;
    line-height: 1.35;
    max-width: 540px;
    margin: 80px auto 60px;
    text-shadow: 0 0 28px rgba(111,214,255,0.22);
    opacity: 0.92;
  }

  /* ----------------------------------------------------------
     THEORY BRANCHES — c7 visual payoff.
     Four hypothesis cards at the top, lines descending, "anomaly"
     terminal at the bottom. Built progressively as the reader
     scrolls the chapter — each card fades in, lines draw, then
     the terminal receives them. Reveal is scroll-triggered by JS
     adding `.tb-r1`, `.tb-r2`, `.tb-r3` classes.
     ---------------------------------------------------------- */
  .theory-branches {
    margin: 50px auto 40px;
    max-width: 620px;
    position: relative;
    font-family: "SF Mono", Menlo, monospace;
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 1s ease, transform 1s ease;
  }
  .theory-branches.in-view { opacity: 1; transform: none; }
  .theory-branches .tb-head {
    font-size: 10px;
    letter-spacing: 0.28em;
    color: var(--dim);
    text-align: center;
    margin-bottom: 22px;
  }
  .theory-branches .tb-row {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    position: relative;
    z-index: 2;
  }
  .theory-branches .tb-card {
    border: 1px solid rgba(111,214,255,0.18);
    background: rgba(4,10,22,0.55);
    padding: 12px 10px;
    text-align: center;
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 0.9s ease, transform 0.9s ease, border-color 0.9s ease;
  }
  .theory-branches.tb-r1 .tb-card[data-branch="1"],
  .theory-branches.tb-r1 .tb-card[data-branch="2"],
  .theory-branches.tb-r1 .tb-card[data-branch="3"],
  .theory-branches.tb-r1 .tb-card[data-branch="4"] {
    opacity: 1;
    transform: none;
    border-color: rgba(111,214,255,0.42);
  }
  /* staggered card reveal in row 1 */
  .theory-branches.tb-r1 .tb-card[data-branch="1"] { transition-delay: 0.0s; }
  .theory-branches.tb-r1 .tb-card[data-branch="2"] { transition-delay: 0.25s; }
  .theory-branches.tb-r1 .tb-card[data-branch="3"] { transition-delay: 0.50s; }
  .theory-branches.tb-r1 .tb-card[data-branch="4"] { transition-delay: 0.75s; }
  .theory-branches .tb-label {
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 6px;
  }
  .theory-branches .tb-desc {
    font-size: 11px;
    line-height: 1.5;
    color: var(--ink);
    opacity: 0.78;
  }
  .theory-branches .tb-lines {
    position: absolute;
    left: 0; right: 0;
    top: 100px;  /* below the card row */
    width: 100%;
    height: 140px;
    pointer-events: none;
    z-index: 1;
  }
  .theory-branches .tb-line {
    fill: none;
    stroke: rgba(111,214,255,0.55);
    stroke-width: 1.1;
    stroke-dasharray: 400;
    stroke-dashoffset: 400;
    transition: stroke-dashoffset 1.4s ease;
  }
  .theory-branches.tb-r2 .tb-line[data-branch="1"] { transition-delay: 0.0s; stroke-dashoffset: 0; }
  .theory-branches.tb-r2 .tb-line[data-branch="2"] { transition-delay: 0.15s; stroke-dashoffset: 0; }
  .theory-branches.tb-r2 .tb-line[data-branch="3"] { transition-delay: 0.30s; stroke-dashoffset: 0; }
  .theory-branches.tb-r2 .tb-line[data-branch="4"] { transition-delay: 0.45s; stroke-dashoffset: 0; }
  .theory-branches .tb-terminal {
    margin-top: 140px;  /* matches the SVG height so lines converge */
    text-align: center;
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 1.2s ease, transform 1.2s ease;
  }
  .theory-branches.tb-r3 .tb-terminal {
    opacity: 1;
    transform: none;
    transition-delay: 0.3s;
  }
  .theory-branches .tb-terminal-label {
    font-size: 11px;
    letter-spacing: 0.32em;
    color: var(--dim);
    margin-bottom: 18px;
    text-transform: uppercase;
  }
  .theory-branches .tb-terminal-word {
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: clamp(56px, 10vw, 120px);
    font-weight: 400;
    color: var(--accent);
    letter-spacing: 0.04em;
    line-height: 1;
    text-shadow: 0 0 44px rgba(111,214,255,0.28);
  }
  @media (max-width: 640px) {
    .theory-branches .tb-row { grid-template-columns: repeat(2, 1fr); gap: 8px; }
    .theory-branches .tb-lines { display: none; }
    .theory-branches .tb-terminal { margin-top: 24px; }
  }

  /* ----------------------------------------------------------
     FINAL CALL MOMENT — c11 closer (8 second isolated whale call)
     ---------------------------------------------------------- */
  .first-call, .final-call {
    margin: 60px auto 40px;
    max-width: 620px;
    text-align: center;
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 2s ease, transform 2s ease;
  }
  .first-call.in-view, .first-call.show,
  .final-call.show { opacity: 1; transform: none; }
  #first-call-canvas {
    display: block;
    margin: 0 auto;
    width: 100%;
    max-width: 600px;
    height: auto;
    aspect-ratio: 600 / 80;
  }
  .first-call .fc-cap {
    opacity: 0;
    transition: opacity 1.2s ease;
    margin-top: 14px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
  }
  .first-call.wave-done .fc-cap { opacity: 1; }
  #final-call-canvas {
    display: block;
    margin: 0 auto;
    width: 100%;
    max-width: 600px;
    height: auto;
    aspect-ratio: 600 / 80;
  }
  .final-call .fc-cap {
    margin-top: 14px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
  }

  /* ----------------------------------------------------------
     CHAPTER MENU — small persistent index, top-left
     ---------------------------------------------------------- */
  #chapter-menu-btn {
    position: fixed;
    top: 18px;
    left: 22px;
    z-index: var(--z-toolbar);
    background: rgba(10,18,30,0.65);
    border: 1px solid rgba(111,214,255,0.45);
    color: var(--ink);
    padding: 9px 14px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.26em;
    text-transform: uppercase;
    height: 30px;
    cursor: pointer;
    transition: background 0.4s, border-color 0.4s;
    display: inline-flex;
    align-items: center;
    gap: 8px;
  }
  #chapter-menu-btn .cm-icon {
    font-size: 14px;
    line-height: 1;
    letter-spacing: 0;
    color: var(--accent);
  }
  #chapter-menu-btn .cm-label {
    font-size: 10px;
    letter-spacing: 0.26em;
  }
  #chapter-menu-btn:hover {
    background: rgba(111,214,255,0.18);
    border-color: var(--accent);
  }
  body.post-transmit-active #chapter-menu-btn { display: none; }
  /* Gated on first visit: chapter selection is only available after
     the reader has reached the finale once. Flag set in localStorage
     by wireStoryCompletionFlag; body class applied on load. */
  body:not(.story-completed) #chapter-menu-btn,
  body:not(.story-completed) #chapter-menu { display: none; }
  body.story-completed #intro .first-visit-note { display: none; }
  #intro .first-visit-note {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.26em;
    text-transform: uppercase;
    color: rgba(138, 160, 189, 0.65);
    margin-top: 12px;
    margin-bottom: 16px;
  }
  #chapter-menu {
    position: fixed;
    top: 64px;
    left: 22px;
    z-index: var(--z-menu);
    width: 260px;
    background: rgba(2,8,18,0.94);
    border: 1px solid rgba(111,214,255,0.4);
    backdrop-filter: blur(10px);
    padding: 18px 20px 16px;
    opacity: 0;
    transform: translateY(-8px);
    pointer-events: none;
    transition: opacity 0.35s ease, transform 0.35s ease;
  }
  #chapter-menu.open { opacity: 1; transform: none; pointer-events: auto; }
  #chapter-menu .cm-head {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 14px;
    padding-bottom: 8px;
    border-bottom: 1px solid rgba(111,214,255,0.25);
  }
  #chapter-menu ol { list-style: none; padding: 0; margin: 0; }
  #chapter-menu li {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.06em;
    color: var(--ink);
    padding: 7px 6px;
    cursor: pointer;
    border-radius: 1px;
    transition: background 0.2s, color 0.2s;
    display: flex;
    gap: 10px;
  }
  #chapter-menu li:hover { background: rgba(111,214,255,0.10); color: var(--accent); }
  #chapter-menu li.current { color: var(--accent); }
  #chapter-menu li.current::before { content: "▸"; position: absolute; margin-left: -14px; }
  #chapter-menu .num { color: var(--dim); font-size: 9.5px; width: 22px; }
  #chapter-menu .cm-foot {
    margin-top: 14px;
    padding-top: 10px;
    border-top: 1px solid rgba(111,214,255,0.18);
    font-family: "SF Mono", Menlo, monospace;
    font-size: 8px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--dim);
    text-align: center;
  }
  @media (max-width: 720px) {
    #chapter-menu-btn { left: auto; right: 64px; }
    #chapter-menu { left: auto; right: 14px; width: 240px; }
  }

  /* ----------------------------------------------------------
     SOUND CAPTION strip — accessibility/clarity for cue presses
     ---------------------------------------------------------- */
  #sound-caption {
    position: fixed;
    bottom: 60px;
    left: 50%;
    transform: translateX(-50%) translateY(8px);
    z-index: var(--z-toolbar);
    pointer-events: none;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--accent);
    text-shadow: 0 0 12px rgba(0,0,0,0.85), 0 0 4px rgba(0,0,0,0.85);
    opacity: 0;
    transition: opacity 0.5s ease, transform 0.5s ease;
    text-align: center;
    white-space: nowrap;
  }
  #sound-caption.show {
    opacity: 0.95;
    transform: translateX(-50%) translateY(0);
  }
  #sound-progress {
    margin: 6px auto 0;
    width: 180px;
    height: 2px;
    background: rgba(111,214,255,0.15);
    border-radius: 1px;
    overflow: hidden;
  }
  #sound-progress-fill {
    width: 0%;
    height: 100%;
    background: var(--accent);
    opacity: 0.7;
    transition: width 0.25s linear;
  }

  /* tiny floating toast for "enable sound first" feedback */
  #sound-toast {
    position: fixed;
    bottom: 60px; left: 50%;
    transform: translateX(-50%) translateY(20px);
    background: rgba(10,18,30,0.92);
    border: 1px solid rgba(111,214,255,0.55);
    color: var(--accent);
    padding: 10px 18px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    z-index: var(--z-toast);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s ease, transform 0.4s ease;
  }
  #sound-toast.show {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }

  /* kinetic typography — word-by-word reveal */
  .kinetic {
    display: inline-block;
    word-spacing: 0.08em;
    letter-spacing: 0.01em;
  }
  .kinetic .w {
    display: inline-block;
    margin-right: 0.32em;
    opacity: 0;
    transform: translateY(14px);
    /* PERF: filter:blur removed. Per-word filter rasterization was the
       single largest GPU cost on this page (hundreds of words rasterizing
       blur every frame). Opacity + transform alone is fast on every
       device. Visual difference is minimal. */
    transition:
      opacity   0.9s cubic-bezier(.2,.6,.2,1),
      transform 0.9s cubic-bezier(.2,.6,.2,1);
    transition-delay: calc((var(--i, 0) + var(--base-i, 0)) * 0.12s + 0.3s);
  }
  .chapter[data-pace="lyrical"] .kinetic .w {
    transition-duration: 1.4s;
    transition-delay: calc((var(--i, 0) + var(--base-i, 0)) * 0.30s + 0.4s);
  }
  .kinetic .w:last-child { margin-right: 0; }
  .chapter.visible .kinetic .w {
    opacity: 1;
    transform: translateY(0);
  }
  /* impact lines breathe a little more */
  .lead .kinetic { font-size: 1.05em; line-height: 1.55; }

  /* ----------------------------------------------------------
     OPENING — c0's two lines reveal MUCH slower than body text
     so the reader has time to settle into the descent.
     ---------------------------------------------------------- */
  .opening {
    text-align: center;
    font-size: clamp(20px, 2.6vw, 34px);
    line-height: 1.55;
    margin: 0 auto 1.4em;
    max-width: 620px;
    font-style: italic;
    color: var(--ink);
  }
  .opening.quiet { color: var(--accent); font-size: clamp(22px, 2.8vw, 36px); }
  /* c1 prose stays in the same cyan as the opening hook-sub, so the
     color doesn't "leave" until the prose itself says it does. The last
     line of c1 — "Below this layer, the color leaves first." — is the
     visual handoff back to white body text from c2 onward. */
  #c1 p { color: var(--accent); }
  /* the new c0 hook line — concrete factual opener for social-media arrivals */
  .opening.hook-line {
    font-style: normal;
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: clamp(22px, 2.8vw, 36px);
    color: var(--ink);
    line-height: 1.4;
    max-width: 720px;
  }
  /* hook-sub: same serif as hook-line, just in cyan accent so it reads
     as a quieter follow-on. No more small mono caps. */
  .opening.hook-sub {
    font-style: normal;
    font-family: "Iowan Old Style", "Palatino", Georgia, serif;
    font-size: clamp(22px, 2.8vw, 36px);
    letter-spacing: 0;
    text-transform: none;
    color: var(--accent);
    margin-top: 32px;
    margin-bottom: 56px;
    line-height: 1.4;
    max-width: 720px;
  }
  .opening .kinetic { font-size: 1em; }
  /* line 1 (.opening.hook-line): starts after a held beat */
  .opening .kinetic .w {
    transition-delay: calc(var(--i, 0) * 0.30s + 0.6s);
    transition-duration: 1.6s;
  }
  /* line 2 (.opening.hook-sub): waits for line 1's last word to land,
     then begins. Line 1 is ~22 words × 0.3s stagger + 0.6s offset +
     1.6s duration = roughly 8.7s end. We start line 2 at 9s. */
  .opening.hook-sub .kinetic .w {
    transition-delay: calc(var(--i, 0) * 0.30s + 9s);
  }
  /* line 3 (.opening.two): waits for line 2 to finish.
     Line 2 is ~13 words × 0.3s + 9s + 1.6s ≈ 14.5s end. */
  .opening.two .kinetic .w {
    transition-delay: calc(var(--i, 0) * 0.30s + 14.8s);
  }

  /* ----------------------------------------------------------
     INTRO LOCK — while the intro overlay is up, hold ALL kinetic
     words at their initial state so they don't burn through their
     transition behind the overlay before the reader ever sees them.
     ---------------------------------------------------------- */
  body.intro-locked .bw,
  body.intro-locked .kinetic .w {
    opacity: 0 !important;
    transform: translateY(14px) !important;
  }

  /* ----------------------------------------------------------
     ACCESSIBILITY — honor the OS-level "reduce motion" setting.
     Text reveals instantly. Letters and bubbles fade in only.
     Particle motion in canvas is also disabled (handled in JS).
     ---------------------------------------------------------- */
  /* ----------------------------------------------------------
     MOBILE — narrow viewports
     Scale canvases, larger touch targets, hide non-essential
     decoration to keep performance and legibility.
     ---------------------------------------------------------- */
  /* ----------------------------------------------------------
     TABLET — mid-range viewports (721–960px).
     Content column stays centered but gets more breathing room.
     Touch targets enlarged for finger taps.
     ---------------------------------------------------------- */
  @media (max-width: 960px) {
    .chapter { padding: 0 7vw; }
    /* ensure all interactive elements meet 44px minimum touch target */
    .cue, .src-link, #sound, .intro-btn,
    .ls-grid button, #play-sequence-btn,
    #send-call-btn, #sources-btn, #chapter-menu-btn {
      min-height: 44px;
    }
  }

  /* ----------------------------------------------------------
     MOBILE — narrow viewports (≤720px).
     ---------------------------------------------------------- */
  @media (max-width: 720px) {
    body { font-size: 16px; }
    .chapter { padding: 0 6vw; }
    #c0 { top: 30vh; }
    .opening { font-size: clamp(18px, 5.4vw, 26px); }
    .opening.quiet { font-size: clamp(18px, 5.4vw, 26px); }
    #hud { padding: 14px 16px; }
    #hud .depth { font-size: 22px; }
    #sound { padding: 10px 18px; font-size: 11px; bottom: 24px; }
    #sound.on { padding: 7px 11px; font-size: 9px; bottom: 14px; left: 14px; }
    #music-toggle { bottom: 52px; right: 14px; left: auto; padding: 7px 11px; font-size: 9px; }
    #intro h1 { font-size: clamp(42px, 13vw, 80px); }
    #intro h1 .sub { font-size: 0.16em; letter-spacing: 0.4em; }
    #intro .invite { font-size: 9.5px; letter-spacing: 0.24em; }
    #intro button { padding: 14px 24px; font-size: 10px; letter-spacing: 0.26em; }
    /* canvases scale to viewport width */
    #map-canvas, #spec-canvas { width: 100% !important; height: auto !important; }
    .media { padding: 12px; }
    .media .cap { font-size: 8.5px; flex-direction: column; gap: 4px; align-items: flex-start; }
    /* pull quote — smaller, less padding */
    .pullquote { padding: 8vh 6vw; font-size: clamp(20px, 5.6vw, 30px); }
    .pullquote::before, .pullquote::after { font-size: 1.6em; }
    /* transmit panel */
    .send-call { padding: 50px 6vw 60px; }
    .send-call h3 { font-size: clamp(22px, 5.6vw, 30px); }
    .send-call p { font-size: 14px; }
    #send-call-btn { width: 120px; height: 120px; font-size: 10px; }
    #send-call-btn::before { font-size: 26px; }
    /* letters / artifacts stages */
    #letters-stage { height: 70vh; }
    #artifacts-stage { height: 80vh; }
    .letter { width: 84vw; margin-left: -42vw; padding: 18px 20px 16px; font-size: 14px; }
    .artifact { width: 140px; height: 140px; padding: 16px; }
    .artifact .ti { font-size: 12px; }
    .artifact .au { font-size: 9px; }
    /* hide grain on mobile — perf */
    #grain { display: none; }
    /* finale split — stack the labels and reduce ring radii are JS-managed */
  }

  @media (prefers-reduced-motion: reduce) {
    .bw, .kinetic .w {
      opacity: 1 !important;
      filter: none !important;
      transform: none !important;
      transition: none !important;
    }
    .letter, .letter.active {
      animation: none !important;
      position: absolute;
      left: 50%;
      top: 50%;
      margin: -90px 0 0 -160px;
      opacity: 1;
    }
    .artifact { transition: opacity 0.3s !important; }
    #intro h1, #intro .invite, #intro button { animation-duration: 0.6s !important; }
  }

  /* the Letters chapter — full bleed overlay */
  #c9 { max-width: 920px; }
  #letters-stage {
    position: relative;
    height: 60vh;
    margin: 24px 0 8px;
    border: 1px solid rgba(111,214,255,0.18);
    background: rgba(2,6,16,0.45);
    overflow: hidden;
  }
  .letter {
    position: absolute;
    width: 320px;
    left: 50%;
    margin-left: -160px;
    top: 100%;
    padding: 22px 24px 20px;
    background: rgba(232,238,247,0.96);
    color: #0a1424;
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: 15px;
    line-height: 1.6;
    box-shadow: 0 16px 50px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.06);
    cursor: pointer;
    opacity: 0;
    transform-origin: center;
    will-change: top, opacity, transform;
  }
  /* sequential reveal — one letter rises through the stage at a time */
  @keyframes letterRise {
    0%   { top: 95%; opacity: 0; transform: translateY(0) rotate(-2deg); }
    14%  { opacity: 1; }
    50%  { top: 35%; opacity: 1; transform: translateY(0) rotate(0deg); }
    86%  { opacity: 1; }
    100% { top: -25%; opacity: 0; transform: translateY(0) rotate(2deg); }
  }
  .letter.active {
    animation: letterRise 9s cubic-bezier(.45,.05,.55,.95) forwards;
  }
  .letter .from {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: #6b7a92;
    margin-bottom: 8px;
    display: block;
  }
  .letter .ko { color: #5a6a82; font-style: italic; display: block; margin-top: 6px; font-size: 12px; }
  .letter:hover {
    box-shadow: 0 18px 50px rgba(0,0,0,0.8), 0 0 0 1px rgba(111,214,255,0.6);
  }
  .letter.focus {
    z-index: 50 !important;
    transform: translate(var(--fx), var(--fy)) scale(1.35) rotate(0deg) !important;
    box-shadow: 0 30px 80px rgba(0,0,0,0.9), 0 0 0 1px rgba(111,214,255,0.9);
  }
  #letter-counter {
    position: absolute;
    top: 12px; right: 14px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--accent);
    background: rgba(0,0,0,0.5);
    padding: 6px 10px;
    border: 1px solid rgba(111,214,255,0.4);
    z-index: 60;
  }
  #letter-counter b { color: var(--ink); font-family: inherit; }

  /* migration map */
  #map-canvas { aspect-ratio: 700 / 460; width: 100%; height: auto; }
  /* spectrogram */
  #spec-canvas { aspect-ratio: 16 / 7; }

  /* ----------------------------------------------------------
     BODY-TEXT KINETIC TYPOGRAPHY
     Two pacing tracks via [data-pace] on .chapter:
       default              — readable rate, ~120ms / word
       data-pace="lyrical"  — meditative rate, ~300ms / word
     Word index is shared across each chapter so paragraphs
     sequence instead of all animating in parallel.
     ---------------------------------------------------------- */
  .bw {
    display: inline-block;
    opacity: 0;
    transform: translateY(8px);
    /* PERF: filter:blur removed (see .kinetic .w note above). The single
       biggest GPU win on this page — body text was animating per-word
       blur on 100+ spans simultaneously, killing scroll perf on most
       devices. Opacity + transform alone is fast everywhere. */
    transition:
      opacity   0.7s cubic-bezier(.2,.6,.2,1),
      transform 0.7s cubic-bezier(.2,.6,.2,1);
    transition-delay: calc(var(--i, 0) * 0.12s + 0.3s);
  }
  /* lyrical chapters: held breath, opening rate */
  .chapter[data-pace="lyrical"] .bw {
    transform: translateY(10px);
    transition-duration: 1.2s;
    transition-delay: calc(var(--i, 0) * 0.30s + 0.4s);
  }
  .chapter.visible .bw {
    opacity: 1;
    transform: none;
  }

  /* ----------------------------------------------------------
     CINEMATIC INTRO OVERLAY
     ---------------------------------------------------------- */
  /* three-stage background video stack
     intro → transition (bubbles) → descent (sunlight)
     each layer is position:fixed at z-index 1, opacity controlled by JS */
  #bg-videos {
    position: fixed;
    inset: 0;
    z-index: var(--z-bg-video);
    pointer-events: none;
  }
  /* loop-wrap holds the STAGE opacity (intro stage = 0.7, descent stage
     starts at 0 and fades in). Inner videos crossfade between two
     copies for a seamless loop — see video.js setupCrossfadeLoop. */
  #bg-videos .loop-wrap {
    position: absolute;
    inset: 0;
    pointer-events: none;
    transition: opacity 1.6s ease;
  }
  #intro-loop-wrap   { opacity: 0.7; }
  #descent-loop-wrap { opacity: 0; }
  /* Sunlit zone background — palindrome coral + jellyfish. Opacity is
     driven by scroll depth (fades in near 55m, out near 200m). */
  #sunlit-loop-wrap  { opacity: 0; }
  /* Inner videos: each loop-wrap contains TWO video elements (the
     original plus a clone added by video.js). They crossfade their
     opacity to hide the loop boundary. The 0.7s ease-in-out matches
     the FADE_SEC constant in video.js. */
  #bg-videos .loop-wrap video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: opacity 0.7s ease-in-out;
  }
  #intro-video, #descent-video { opacity: 1; }
  #transition-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 1.2s ease;
  }

  #bg-vignette {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background:
      radial-gradient(ellipse at 50% 50%, rgba(2,6,16,0.0) 0%, rgba(2,6,16,0.45) 55%, rgba(2,6,16,0.85) 100%);
    transition: opacity 1.6s ease;
  }

  #intro {
    position: fixed;
    inset: 0;
    background: transparent;
    z-index: var(--z-intro);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 8vw;
    transition: opacity 2.4s ease;
  }
  #intro.gone { opacity: 0; pointer-events: none; }
  #intro h1 {
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: clamp(54px, 11vw, 150px);
    font-weight: 400;
    letter-spacing: 0.06em;
    margin: 0;
    color: var(--ink);
    opacity: 0;
    animation: introFade 2.6s 0.6s forwards;
    line-height: 0.95;
  }
  #intro h1 .sub {
    display: block;
    font-size: 0.13em;
    letter-spacing: 0.55em;
    color: var(--accent);
    margin-top: 28px;
    text-transform: uppercase;
    font-style: normal;
  }
  #intro .hook {
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: clamp(15px, 1.7vw, 19px);
    line-height: 1.55;
    color: var(--ink);
    max-width: 560px;
    margin: 44px auto 0;
    opacity: 0;
    animation: introFade 2.4s 1.8s forwards;
  }
  #intro .invite {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10.5px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    /* Brightened from var(--dim) — was getting lost against the sky-blue
       intro background. Added a soft dark shadow for contrast on bright
       gradient areas without making the type feel heavy. */
    color: rgba(255,255,255,0.92);
    text-shadow: 0 1px 6px rgba(0,0,0,0.55), 0 0 14px rgba(0,0,0,0.35);
    opacity: 0;
    animation: introFade 2s 3.4s forwards;
    margin-top: 30px;
    line-height: 2.2;
  }
  #intro button {
    margin-top: 38px;
    background: transparent;
    color: var(--accent);
    border: 1px solid rgba(111,214,255,0.55);
    padding: 16px 36px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.34em;
    text-transform: uppercase;
    cursor: pointer;
    opacity: 0;
    animation: introFade 1.6s 4.4s forwards;
    transition: background 0.5s, color 0.5s, letter-spacing 0.5s;
  }
  #intro button:hover {
    background: rgba(111,214,255,0.12);
    color: var(--ink);
    letter-spacing: 0.4em;
  }
  /* hide scroll hint during intro so it doesn't collide with the
     begin button area. JS shows it after intro is dismissed. */
  body.intro-locked #hint { display: none; }
  @keyframes introFade { to { opacity: 1; } }
  body.intro-locked { overflow: hidden; height: 100vh; }

  /* FISH SWIM PAIR — transparent VP9 WebM videos that appear during
     the 150m-185m depth window. Positioned full-bleed behind the text
     so the fish read as swimming in the ocean around the prose. */
  #fish-swim-pair {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 2;
  }
  .fish-swim {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: min(92vw, 960px);
    max-height: 72vh;
    object-fit: contain;
    opacity: 0;
    will-change: opacity;
    mix-blend-mode: screen;
    transition: opacity 0.35s linear;
  }
  .fish-swim.fish-mirror {
    transform: translate(-50%, -50%) scaleX(-1);
  }
  /* Full-bleed variant — stretches edge to edge horizontally, height
     follows aspect ratio. Used for the colourful school between c2
     and c3 which should feel like a current filling the frame. */
  .fish-swim.fish-fullwidth {
    width: 100vw;
    max-height: none;
  }
  /* Cover variant — drops the screen blend and fills the viewport
     fully so the video obscures the background instead of letting it
     show through. Used for the bridge video leading into c1. */
  .fish-swim.fish-cover {
    mix-blend-mode: normal;
    width: 100vw;
    height: 100vh;
    max-height: none;
    object-fit: cover;
    background: #01060e;
  }

  /* ----------------------------------------------------------
     c0 SPLASH SEQUENCE — three water-splash transitions that
     cover the line swaps in the opening chapter. Orchestrated
     by js/c0-sequence.js. Screen-blended so the dark frames
     disappear and only the water reads.
     ---------------------------------------------------------- */
  #c0-splashes {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 50;
  }
  .c0-splash-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.45s ease;
    will-change: opacity;
  }
  .c0-splash-video.show { opacity: 1; }

  /* c0-stage stacks all three opening lines in one grid cell so they
     occupy the same position. Lines that aren't currently active are
     held at opacity 0 (via .c0-line-staged or .c0-fading-out). */
  #c0 .c0-stage {
    display: grid;
    grid-template-areas: "stage";
    min-height: 32vh;
  }
  #c0 .c0-stage > .opening {
    grid-area: stage;
  }
  /* Initial staged state for lines 2 and 3 — no transition, just
     invisible until the orchestrator swaps them in. */
  #c0 .c0-line-staged {
    opacity: 0;
    pointer-events: none;
  }
  /* Smooth fade-out for a line being washed away by a splash. Tuned
     shorter (0.75 s) so the text clears quickly and reads as being
     rinsed away rather than drifting. */
  #c0 .c0-fading-out {
    opacity: 0 !important;
    pointer-events: none;
    transition: opacity 0.75s ease-out;
  }

  /* Suppress the "Scroll to descend" hint while the splash sequence
     runs; removed once splash 3 finishes so the hint can fade in. */
  body.c0-splash-active #hint { display: none !important; }

  /* ----------------------------------------------------------
     INFO BOX ENLARGE — click any .enlargeable box to view it
     as a centered modal overlay. Wired up in info-expand.js.
     ---------------------------------------------------------- */
  .enlargeable {
    cursor: zoom-in;
    transition: box-shadow 0.3s ease, border-color 0.3s ease;
  }
  .enlargeable:hover {
    box-shadow: 0 0 0 1px rgba(111, 214, 255, 0.5),
                0 6px 24px rgba(111, 214, 255, 0.15);
  }
  .enlargeable:focus-visible {
    outline: 1px solid rgba(111, 214, 255, 0.8);
    outline-offset: 4px;
  }
  /* Fixed body-level overlay that hosts the enlarged box while active.
     The expanded element is moved into this, escaping any ancestor
     transform (.chapter has translateY which otherwise breaks fixed
     positioning). Pointer-events only auto on the expanded child so
     clicks elsewhere fall through to the body click-to-dismiss. */
  #info-expand-overlay {
    position: fixed;
    inset: 0;
    z-index: 99999;
    pointer-events: none;
  }
  body.enlarge-active #info-expand-overlay { pointer-events: auto; }
  .enlargeable.expanded {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(1200px, 94vw);
    max-height: 92vh;
    overflow: auto;
    cursor: zoom-out;
    box-shadow: 0 24px 80px rgba(0, 0, 0, 0.85);
  }
  .enlargeable.expanded canvas,
  .enlargeable.expanded svg {
    width: 100% !important;
    height: auto !important;
    max-height: calc(92vh - 100px);
  }
  body.enlarge-active::before {
    content: "";
    position: fixed;
    inset: 0;
    background: rgba(1, 6, 14, 0.92);
    backdrop-filter: blur(3px);
    z-index: 99997;
  }
  @media (max-width: 720px) {
    .enlargeable.expanded { width: 96vw; max-height: 88vh; }
  }

  /* progress line on left edge — kept for compatibility but hidden;
     the new #depth-rail below replaces it as the primary indicator. */
  #progress {
    display: none;
  }

  /* ----------------------------------------------------------
     DEPTH RAIL — primary depth indicator, left edge.
     Single source of truth for depth (HUD's numeric depth row
     below is hidden). The rail represents the ENTIRE document,
     so when the reader reaches the end of the main story the
     marker sits around 90% and a faint pulsing notch below
     hints at unexplored depth (the secret ending).
     ---------------------------------------------------------- */
  #depth-rail {
    position: fixed;
    left: 22px;
    top: 50%;
    transform: translateY(-50%);
    width: 52px;
    height: 66vh;
    max-height: 640px;
    display: flex;
    flex-direction: column;
    align-items: center;
    z-index: var(--z-hint);
    pointer-events: none;
    font-family: "SF Mono", Menlo, monospace;
    opacity: 0;
    transition: opacity 1.2s ease;
  }
  body.intro-locked #depth-rail { opacity: 0; pointer-events: none; }
  body:not(.intro-locked) #depth-rail { opacity: 1; }
  #depth-rail .dr-head {
    font-size: 8.5px;
    letter-spacing: 0.34em;
    color: rgba(111, 214, 255, 0.55);
    margin-bottom: 12px;
    text-transform: uppercase;
  }
  #depth-rail .dr-scale {
    position: relative;
    width: 100%;
    flex: 1;
  }
  #depth-rail .dr-track {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 0; bottom: 0;
    width: 2px;
    background: linear-gradient(180deg,
      rgba(111,214,255,0.18),
      rgba(111,214,255,0.10) 90%,
      rgba(111,214,255,0.04));
  }
  #depth-rail .dr-fill {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    top: 0;
    width: 2px;
    height: 0%;
    background: linear-gradient(180deg, rgba(111,214,255,0.95), rgba(111,214,255,0.55));
    box-shadow: 0 0 6px rgba(111,214,255,0.45);
  }
  #depth-rail .dr-notches { position: absolute; inset: 0; }
  #depth-rail .dr-notch {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: 14px;
    height: 1px;
    background: rgba(111,214,255,0.4);
    transition: background 0.4s ease, width 0.4s ease;
  }
  #depth-rail .dr-notch.passed {
    background: rgba(111,214,255,0.85);
  }
  #depth-rail .dr-notch.secret {
    width: 22px;
    height: 1.5px;
    background: rgba(111,214,255,0.28);
    animation: drSecretPulse 3.4s ease-in-out infinite;
  }
  #depth-rail .dr-marker {
    position: absolute;
    left: 50%;
    top: 0%;
    transform: translate(-50%, -50%);
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 10px rgba(111,214,255,0.85), 0 0 2px rgba(255,255,255,0.6);
  }
  #depth-rail .dr-marker-label {
    position: absolute;
    left: 16px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 9.5px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: rgba(220, 235, 255, 0.85);
    white-space: nowrap;
    text-shadow: 0 1px 6px rgba(0,0,0,0.9);
  }
  @keyframes drSecretPulse {
    0%, 100% { opacity: 0.32; box-shadow: 0 0 0 0 rgba(111,214,255,0);   }
    50%      { opacity: 0.9;  box-shadow: 0 0 6px 2px rgba(111,214,255,0.35); }
  }

  /* Hide HUD numeric depth row — the rail is the primary source of
     truth. Keep Hz, chapter counter, listening pulse. */
  #hud .label,
  #hud .depth { display: none; }

  @media (max-width: 720px) {
    #depth-rail { left: 8px; width: 34px; height: 58vh; }
    #depth-rail .dr-marker-label { display: none; }
    #depth-rail .dr-head { font-size: 7.5px; letter-spacing: 0.28em; }
  }
  @media (prefers-reduced-motion: reduce) {
    #depth-rail .dr-notch.secret { animation: none; opacity: 0.6; }
  }

  /* ----------------------------------------------------------
     CULTURAL IMPACT chapter — artifacts as bioluminescent bubbles
     ---------------------------------------------------------- */
  #c9b { max-width: 920px; }
  #artifacts-stage {
    position: relative;
    height: 70vh;
    margin: 24px 0 8px;
    border: 1px solid rgba(111,214,255,0.18);
    background:
      radial-gradient(ellipse at 50% 110%, rgba(8,30,55,0.55), transparent 70%),
      radial-gradient(ellipse at 50% 0%, rgba(20,40,70,0.35), transparent 60%),
      #02060f;
    overflow: hidden;
  }
  /* ----------------------------------------------------------
     ARTIFACT BUBBLES — collapsed bubble morphs into expanded
     card IN PLACE when clicked. Other bubbles dim and fade
     back. The embedded video lives inside what used to be
     the bubble itself, not in a separate modal.
     ---------------------------------------------------------- */
  .artifact {
    position: absolute;
    width: 180px;
    height: 180px;
    border-radius: 50%;
    background:
      radial-gradient(circle at 35% 30%, rgba(180,220,255,0.22), rgba(20,40,70,0.7) 55%, rgba(2,6,16,0.9));
    border: 1px solid rgba(111,214,255,0.4);
    box-shadow:
      0 0 40px rgba(111,214,255,0.18),
      inset 0 0 30px rgba(111,214,255,0.12);
    cursor: pointer;
    transition:
      width 0.5s cubic-bezier(.22,.7,.1,1),
      height 0.5s cubic-bezier(.22,.7,.1,1),
      transform 0.5s cubic-bezier(.22,.7,.1,1),
      border-radius 0.5s cubic-bezier(.22,.7,.1,1),
      padding 0.5s cubic-bezier(.22,.7,.1,1),
      background 0.4s ease,
      box-shadow 0.4s ease,
      opacity 0.4s ease;
    overflow: hidden;
  }
  .artifact:hover:not(.expanded) {
    box-shadow:
      0 0 70px rgba(111,214,255,0.4),
      inset 0 0 40px rgba(111,214,255,0.22);
  }
  .artifact .compact {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 22px;
    transition: opacity 0.3s ease;
  }
  .artifact .compact .yr {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.22em;
    color: var(--accent);
    margin-bottom: 5px;
  }
  .artifact .compact .ty {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 8.5px;
    letter-spacing: 0.18em;
    color: var(--dim);
    margin-bottom: 10px;
    text-transform: uppercase;
  }
  .artifact .compact .ti {
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: 14px;
    line-height: 1.25;
    color: var(--ink);
    margin-bottom: 5px;
    font-style: italic;
  }
  .artifact .compact .au {
    font-size: 10px;
    color: var(--dim);
    line-height: 1.3;
  }
  .artifact .full {
    display: none;
    padding: 30px 36px 34px;
    color: var(--ink);
    font-family: "Iowan Old Style", Georgia, serif;
    text-align: left;
  }
  /* expanded state */
  .artifact.expanded {
    width: min(680px, 92vw);
    height: auto;
    min-height: 480px;
    border-radius: 4px;
    background: rgba(2,8,18,0.97);
    box-shadow:
      0 30px 100px rgba(0,0,0,0.85),
      0 0 0 1px rgba(111,214,255,0.5),
      0 0 60px rgba(111,214,255,0.15);
    z-index: 100;
    cursor: default;
    overflow-y: auto;
    max-height: 92vh;
  }
  .artifact.expanded .compact { opacity: 0; pointer-events: none; }
  .artifact.expanded .full { display: block; opacity: 0; animation: fullFadeIn 0.4s ease 0.25s forwards; }
  @keyframes fullFadeIn { to { opacity: 1; } }

  /* dim non-expanded bubbles when one is open */
  #artifacts-stage.has-expanded .artifact:not(.expanded) {
    opacity: 0.1;
    pointer-events: none;
  }
  #artifacts-stage.has-expanded #artifacts-header {
    opacity: 0.15;
    transition: opacity 0.5s ease;
  }

  /* expanded card content */
  .artifact .full .header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    margin-bottom: 14px;
  }
  .artifact .full .meta {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.22em;
    color: var(--accent);
    text-transform: uppercase;
    padding-top: 4px;
  }
  .artifact .full .close {
    background: transparent;
    border: 1px solid rgba(111,214,255,0.4);
    color: var(--dim);
    width: 32px;
    height: 32px;
    border-radius: 50%;
    font-size: 14px;
    cursor: pointer;
    transition: background 0.3s, color 0.3s, border-color 0.3s;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .artifact .full .close:hover {
    background: rgba(111,214,255,0.15);
    color: var(--ink);
    border-color: rgba(111,214,255,0.8);
  }
  .artifact .full h3 {
    font-size: clamp(22px, 3vw, 30px);
    font-weight: 400;
    font-style: italic;
    margin: 0 0 6px;
    line-height: 1.2;
    color: var(--ink);
  }
  .artifact .full .au {
    font-size: 12px;
    color: var(--dim);
    margin-bottom: 18px;
    font-family: "SF Mono", Menlo, monospace;
    letter-spacing: 0.04em;
  }
  .artifact .full .body {
    font-size: 15px;
    line-height: 1.7;
    color: var(--ink);
  }
  .artifact .full .body em { color: var(--accent); font-style: italic; }
  /* embedded media INSIDE the expanded bubble */
  .artifact .full .embed-yt {
    position: relative;
    padding-bottom: 56.25%;
    height: 0;
    margin: 22px 0 10px;
    overflow: hidden;
    border: 1px solid rgba(111,214,255,0.3);
    background: #000;
  }
  .artifact .full .embed-yt iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
  }
  .artifact .full .embed-spotify {
    margin: 22px 0 10px;
    border: 1px solid rgba(111,214,255,0.3);
    border-radius: 6px;
    overflow: hidden;
  }
  .artifact .full .embed-spotify iframe {
    width: 100%;
    height: 152px;
    border: 0;
    display: block;
  }
  .artifact .full .external-link {
    display: inline-block;
    margin-top: 18px;
    padding: 11px 20px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--accent);
    border: 1px solid rgba(111,214,255,0.55);
    text-decoration: none;
    transition: background 0.4s, color 0.4s, letter-spacing 0.4s;
    background: rgba(111,214,255,0.04);
  }
  .artifact .full .external-link:hover {
    background: rgba(111,214,255,0.18);
    color: var(--ink);
    letter-spacing: 0.26em;
  }

  /* extended bottom on finale so the call can continue past the words */
  #c11 { padding-bottom: 40vh; }

  /* ----------------------------------------------------------
     FULL SILENCE OVERLAY — chapter 8 climax
     The whole screen goes black. All sound fades to nothing.
     One line of italic serif at massive size. Six seconds of
     pure silence. Then the world comes back.
     ---------------------------------------------------------- */
  #full-silence {
    position: fixed;
    inset: 0;
    background: rgba(2,6,14,0.72);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    z-index: var(--z-silence);
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 8vw;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.5s cubic-bezier(.2,.6,.2,1);
  }
  #full-silence.active {
    opacity: 1;
    pointer-events: auto;
  }
  #full-silence .text {
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-weight: 400;
    font-size: clamp(34px, 7.5vw, 100px);
    line-height: 1.1;
    color: var(--ink);
    letter-spacing: 0.005em;
    max-width: 1100px;
    opacity: 0;
    transform: translateY(12px);
    transition:
      opacity   0.55s cubic-bezier(.2,.6,.2,1) 0.15s,
      transform 0.55s cubic-bezier(.2,.6,.2,1) 0.15s;
    text-shadow: 0 2px 40px rgba(0,0,0,0.9);
  }
  #full-silence.active .text {
    opacity: 1;
    transform: translateY(0);
  }
  /* the silence trigger marker is invisible */
  #c8-silence-trigger { height: 1px; margin: 4px 0 0; }
  /* lock body scroll while silence is active */
  body.silence-locked { overflow: hidden; }

  /* Letters → Cultural Impact connecting line at the top of the stage */
  #artifacts-header {
    position: absolute;
    top: 24px;
    left: 0;
    right: 0;
    text-align: center;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9.5px;
    letter-spacing: 0.22em;
    color: var(--accent);
    opacity: 0.7;
    z-index: 5;
    padding: 0 24px;
  }

  /* ----------------------------------------------------------
     PULL QUOTE — breaks the chapter grid
     For moments that need to escape the body column.
     ---------------------------------------------------------- */
  .pullquote {
    position: relative;
    width: 100vw;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
    padding: 18vh 8vw 16vh;
    text-align: center;
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: clamp(28px, 5vw, 60px);
    line-height: 1.35;
    color: var(--ink);
    max-width: none !important;
    text-shadow: 0 2px 30px rgba(0,0,0,0.9);
  }
  /* the inner <p> inherits sizing from .pullquote — strip the chapter
     paragraph margin and text-shadow that would otherwise stack */
  blockquote.pullquote p {
    margin: 0;
    font: inherit;
    color: inherit;
    text-shadow: inherit;
  }
  .pullquote::before,
  .pullquote::after {
    content: '"';
    display: block;
    font-size: 2.2em;
    color: var(--accent);
    opacity: 0;
    line-height: 0.5;
    font-style: normal;
    /* marks fade in when the pullquote becomes .in-view */
    transition: opacity 1.4s ease 1s;
  }
  .pullquote::before { margin-bottom: 48px; }
  .pullquote::after  { margin-top: 56px; }
  .pullquote cite {
    display: block;
    margin-top: 38px;
    font-size: 0.32em;
    font-style: normal;
    color: var(--dim);
    letter-spacing: 0.22em;
    text-transform: uppercase;
    font-family: "SF Mono", Menlo, monospace;
  }
  /* pullquote words — faster than default so the quote lands quickly */
  .pullquote .bw {
    transition-delay: calc(var(--i, 0) * 0.05s + 0.15s);
    transition-duration: 0.45s;
  }
  .pullquote.in-view::before,
  .pullquote.in-view::after { opacity: 0.45; }
  .pullquote cite { opacity: 0; transition: opacity 0.6s ease 0.3s; }
  .pullquote.in-view cite { opacity: 1; }

  /* ----------------------------------------------------------
     LETTERS + ARTIFACTS — full-bleed stages that break out of
     the chapter's max-width and span the viewport.
     ---------------------------------------------------------- */
  #letters-stage, #artifacts-stage {
    position: relative;
    width: 100vw;
    left: 50%;
    right: 50%;
    margin-top: 80px;       /* breathing room from the prose above */
    margin-bottom: 60px;    /* breathing room from the caption below */
    margin-left: -50vw;
    margin-right: -50vw;
    height: 78vh;
  }
  #letters-stage { border-left: 0; border-right: 0; }
  #artifacts-stage { border-left: 0; border-right: 0; }

  /* ----------------------------------------------------------
     TRANSMIT PANEL — the participation moment
     The reader sends their own call into the dark.
     ---------------------------------------------------------- */
  .send-call {
    position: relative;
    width: 100vw;
    left: 50%;
    right: 50%;
    margin: 50px -50vw 20px;
    padding: 100px 8vw 100px;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    background: none;
    max-width: none !important;
  }
  .send-call .cap {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 18px;
  }
  .send-call h3 {
    font-family: "Iowan Old Style", Georgia, serif;
    font-size: clamp(26px, 3.4vw, 40px);
    font-weight: 400;
    font-style: italic;
    color: var(--ink);
    margin: 0 0 18px;
    line-height: 1.25;
  }
  .send-call p {
    max-width: 520px;
    margin: 0 auto 36px;
    color: var(--dim);
    font-size: 15px;
    line-height: 1.65;
    font-style: italic;
  }
  #send-call-btn {
    position: relative;
    width: 140px;
    height: 140px;
    border-radius: 50%;
    background:
      radial-gradient(circle at 35% 30%, rgba(180,220,255,0.25), rgba(20,40,70,0.85) 60%, rgba(2,6,16,0.95));
    border: 1px solid rgba(111,214,255,0.15);
    color: rgba(111,214,255,0.15);
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    cursor: default;
    box-shadow: none;
    opacity: 0.3;
    pointer-events: none;
    transition: transform 0.4s, box-shadow 1.2s ease, border-color 1.2s ease,
      color 1.2s ease, opacity 1.2s ease;
  }
  #send-call-btn.ready {
    border-color: rgba(111,214,255,0.6);
    color: var(--accent);
    box-shadow:
      0 0 50px rgba(111,214,255,0.25),
      inset 0 0 30px rgba(111,214,255,0.18);
    opacity: 1;
    cursor: pointer;
    pointer-events: auto;
  }
  #send-call-btn::before {
    content: '⊙';
    display: block;
    font-size: 32px;
    margin-bottom: 6px;
    color: var(--ink);
  }
  #send-call-btn:hover {
    transform: scale(1.05);
    box-shadow:
      0 0 80px rgba(111,214,255,0.45),
      inset 0 0 40px rgba(111,214,255,0.3);
  }
  #send-call-btn:active { transform: scale(0.97); }
  #send-call-btn.transmitting {
    animation: none;
    border-color: rgba(255,200,120,0.9);
    color: rgba(255,200,120,0.9);
    box-shadow:
      0 0 100px rgba(255,200,120,0.5),
      inset 0 0 50px rgba(255,200,120,0.35);
  }
  .send-call .counter {
    margin-top: 30px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--dim);
  }
  .send-call .counter b { color: var(--accent); font-family: inherit; }

  /* ----------------------------------------------------------
     DOT INDEX — vertical chapter quick-jump on the right edge.
     Hidden by default. Opt-in by appending ?dots to the URL or
     by setting document.body.classList.add('dots-on').
     ---------------------------------------------------------- */
  #dot-index {
    position: fixed;
    top: 50%;
    right: 16px;
    transform: translateY(-50%);
    z-index: var(--z-toolbar);
    display: none;
    flex-direction: column;
    gap: 10px;
    pointer-events: auto;
  }
  body.dots-on #dot-index { display: flex; }
  #dot-index a {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: rgba(111,214,255,0.20);
    border: 1px solid rgba(111,214,255,0.45);
    text-decoration: none;
    position: relative;
    transition: background 0.3s, transform 0.3s, border-color 0.3s, box-shadow 0.3s;
  }
  #dot-index a:hover {
    background: rgba(111,214,255,0.6);
    border-color: rgba(111,214,255,0.9);
    transform: scale(1.45);
  }
  #dot-index a:hover::after {
    content: attr(data-title);
    position: absolute;
    right: 22px;
    top: 50%;
    transform: translateY(-50%);
    background: rgba(2,8,18,0.94);
    border: 1px solid rgba(111,214,255,0.4);
    color: var(--ink);
    padding: 5px 12px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 9px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    white-space: nowrap;
    pointer-events: none;
  }
  #dot-index a.active {
    background: var(--accent);
    border-color: var(--accent);
    box-shadow: 0 0 12px rgba(111,214,255,0.55);
  }
  @media (max-width: 720px) {
    #dot-index { right: 8px; gap: 8px; }
    #dot-index a { width: 7px; height: 7px; }
  }

  /* ----------------------------------------------------------
     INLINE SOURCE LINKS — quiet dotted underline so a skeptical
     reader can verify a fact without leaving the prose for the
     Sources modal.

     The dotted underline is rendered on the <a> element, which is
     present in the DOM from the start. The link's TEXT is wrapped
     in .bw spans by the kineticize loop and starts at opacity 0,
     fading in word by word. To prevent the underline from appearing
     before its text, the border starts transparent and fades in via
     the chapter visibility cascade with a delay long enough to let
     the link's words land first.
     ---------------------------------------------------------- */
  .chapter p a.src-link {
    color: inherit;
    text-decoration: none;
    border-bottom: 1px dotted transparent;
    padding-bottom: 1px;
    /* not interactive until JS adds .link-ready after the reveal delay */
    pointer-events: none;
    /* per-link delay set by chapters.js based on the first inner .bw
       word's index — defaults to 12s if JS hasn't run yet */
    transition: color 0.3s ease, border-color 1.4s ease var(--src-delay, 12s);
  }
  .chapter.visible p a.src-link {
    border-bottom-color: rgba(111,214,255,0.4);
  }
  /* JS adds .link-ready after --src-delay ms — only then can the
     reader interact with the link */
  .chapter p a.src-link.link-ready {
    pointer-events: auto;
  }
  .chapter p a.src-link.link-ready:hover,
  .chapter p a.src-link.link-ready:focus-visible {
    color: var(--accent);
    border-bottom-color: var(--accent);
    transition-delay: 0s;
  }
  /* NOTE: no :visited override. Browsers snap :visited color changes
     instantly and ignore the delayed transition on the base rule, which
     would cause the dotted underline to appear before the link's text
     on any link the reader has clicked once. The per-link --src-delay
     only works if every state uses the same transition pipeline. */

  /* ============================================================
     PRINT STYLESHEET
     Strip the cinematic chrome and lay the prose out for paper.
     Readers who hit Print or Save As PDF get a clean text version
     with researcher cards, sources, and the post-credits links.
     ============================================================ */
  @media print {
    @page { margin: 1.6cm 1.8cm; }
    /* hide everything that doesn't translate to paper */
    #bg, #bg-static, #bg-videos, #grain, #surface, #hud,
    #sound, #stop-audio-btn, #chapter-menu-btn, #chapter-menu,
    #sources-btn, #sources-modal, #sound-toast, #sound-caption,
    #hint, #progress, #full-silence, #intro, #dot-index,
    #letters-stage, #artifacts-stage,
    .listening-station, .sequence-cue, .send-call,
    .final-call, .anomaly-word, .post-credits {
      display: none !important;
    }
    html, body {
      background: #fff !important;
      color: #000 !important;
      font-family: "Iowan Old Style", Palatino, Georgia, serif !important;
      font-size: 11pt !important;
      line-height: 1.55 !important;
    }
    /* unstack the chapter layout — chapters become normal flow elements */
    #journey { height: auto !important; }
    .chapter {
      position: static !important;
      max-width: 100% !important;
      padding: 0 !important;
      margin: 0 0 1.4em 0 !important;
      opacity: 1 !important;
      transform: none !important;
    }
    .chapter h2 {
      color: #444 !important;
      font-family: "SF Mono", Menlo, monospace !important;
      font-size: 9pt !important;
      letter-spacing: 0.18em;
      text-transform: uppercase;
      margin: 1.4em 0 0.4em;
      page-break-after: avoid;
    }
    #c11 h2.finale-headline {
      font-family: "Iowan Old Style", Georgia, serif !important;
      font-size: 18pt !important;
      color: #000 !important;
      text-transform: none;
      letter-spacing: 0;
      page-break-before: always;
    }
    .chapter p { color: #000 !important; text-shadow: none !important; margin-bottom: 0.7em !important; }
    .chapter p a.src-link { color: #000 !important; border-bottom: 1px solid #888 !important; }
    .chapter p a.src-link::after { content: " (" attr(href) ")"; font-size: 0.75em; color: #666; }
    /* show kinetic words as plain text */
    .bw, .kinetic .w {
      opacity: 1 !important;
      filter: none !important;
      transform: none !important;
      display: inline !important;
    }
    /* show the SR-only twin only when printing — don't double-print */
    .visually-hidden { position: absolute !important; left: -9999px !important; }
    /* researcher cards — flow inline */
    figure.person-card {
      display: block;
      page-break-inside: avoid;
      margin: 1em 0;
      padding: 0.5em 0;
      border-top: 1px solid #ccc;
    }
    .person-avatar { display: none !important; }
    .person-name { font-weight: bold; }
    .person-role { font-style: italic; color: #555; }
    /* pullquote */
    blockquote.pullquote {
      width: auto !important; left: auto !important; right: auto !important;
      margin: 1.5em 2em !important;
      padding: 0.6em 1em !important;
      border-left: 3px solid #888 !important;
      font-size: 11pt !important;
      font-style: italic;
      page-break-inside: avoid;
    }
    blockquote.pullquote::before, blockquote.pullquote::after { display: none !important; }
    blockquote.pullquote cite { color: #555; font-size: 9pt; }
    /* whale chart — show the figcaption text instead of the SVG */
    figure.whale-chart svg { display: none !important; }
    figure.whale-chart .visually-hidden {
      position: static !important; left: auto !important;
      width: auto !important; height: auto !important;
      clip: auto !important; white-space: normal !important;
      display: block; font-style: italic; color: #444;
    }
    /* media boxes — strip the cyan box, leave the canvas image */
    .media, .year-counter, .drift-graphic, .specimen-card {
      border: 1px solid #ccc !important;
      background: #fff !important;
      padding: 0.4em 0.6em !important;
      page-break-inside: avoid;
    }
    canvas { max-width: 100% !important; height: auto !important; }
    /* sources modal — print the contents instead of the modal chrome */
    /* (left out: too long for an inline print pass — modal isn't visible
        in print at all; reader can use the linked URLs that .src-link
        adds via ::after) */
    #deep-dark { display: none !important; }
  }

  /* ----------------------------------------------------------
     SECRET ENDING — the ocean below the ocean.
     Found only by readers who scroll past everything.
     ---------------------------------------------------------- */
  #deep-dark {
    position: absolute;
    left: 0; right: 0;
    top: 3540vh;
    z-index: var(--z-content);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 0 8vw;
    min-height: 100vh;
    opacity: 0;
    transition: opacity 2.4s ease;
  }
  #deep-dark.visible { opacity: 1; }

  .deep-prompt {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 24px;
    transition: opacity 1.8s ease;
  }
  .deep-prompt.dissolved {
    opacity: 0;
    pointer-events: none;
  }
  /* SONAR READOUT — detection label + subtext. Both fade in on a
     stagger once the section is visible. Button arrives last so the
     reader absorbs the detection before being offered a choice. */
  .deep-label {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.36em;
    text-transform: uppercase;
    color: var(--accent);
    opacity: 0;
    transition: opacity 2.2s ease;
  }
  #deep-dark.visible .deep-label {
    opacity: 0.82;
    transition-delay: 0.4s;
  }
  .deep-sublabel {
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: clamp(18px, 2.4vw, 26px);
    color: var(--ink);
    opacity: 0;
    transition: opacity 2.4s ease;
    margin-top: -4px;
  }
  #deep-dark.visible .deep-sublabel {
    opacity: 0.85;
    transition-delay: 2.2s;
  }
  #deep-sing {
    background: transparent;
    border: 1px solid rgba(111,214,255,0.35);
    color: var(--accent);
    font-family: "SF Mono", Menlo, monospace;
    font-size: 10px;
    letter-spacing: 0.32em;
    text-transform: lowercase;
    padding: 12px 32px;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 1.4s ease, background 0.4s, border-color 0.4s;
    margin-top: 14px;
  }
  /* button arrives last — held back so the reader isn't rushed */
  #deep-dark.visible #deep-sing {
    opacity: 0.85;
    pointer-events: auto;
    transition-delay: 4.8s;
  }
  #deep-sing:hover {
    background: rgba(111,214,255,0.10);
    border-color: rgba(111,214,255,0.7);
  }

  /* response waveform — dimmer, thinner than the main call canvases */
  #response-canvas {
    display: block;
    margin: 40px auto 0;
    width: 100%;
    max-width: 600px;
    height: auto;
    aspect-ratio: 600 / 80;
    opacity: 0;
    transition: opacity 2s ease;
  }
  #response-canvas.show { opacity: 1; }

  /* closing line — the last words on the page */
  .deep-closing {
    margin-top: 60px;
    font-family: "Iowan Old Style", Georgia, serif;
    font-style: italic;
    font-size: clamp(14px, 1.8vw, 18px);
    color: var(--dim);
    opacity: 0;
    transition: opacity 3s ease;
  }
  .deep-closing.show { opacity: 0.55; }

  @media (max-width: 720px) {
    #deep-sing { padding: 10px 24px; }
    .deep-label { font-size: 10px; }
    .deep-sublabel { font-size: 18px; }
  }

