  :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: 3500vh;
    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 */
  #sources-modal {
    position: fixed;
    inset: 0;
    background: rgba(2,6,16,0.94);
    display: none;
    align-items: center;
    justify-content: center;
    padding: 6vh 6vw;
    z-index: var(--z-modal);
    backdrop-filter: blur(10px);
    overflow-y: auto;
  }
  #sources-modal.open { display: flex; }
  #sources-modal .inner {
    max-width: 620px;
    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; }

  /* 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;
    z-index: var(--z-silence);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 8vw;
    text-align: center;
    background: rgba(1,3,8,0.98);
    opacity: 0;
    transition: opacity 2.5s ease;
    pointer-events: none;
  }
  .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;
    text-shadow: 0 2px 40px rgba(0,0,0,0.9);
    opacity: 0;
    transform: translateY(18px);
    transition:
      opacity 1.8s cubic-bezier(.2,.6,.2,1),
      transform 1.8s cubic-bezier(.2,.6,.2,1);
    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;
    animation: bob 2.4s infinite ease-in-out;
  }
  .post-transmit-hint.show { visibility: visible; opacity: 0.7; }

     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 var(--accent);
    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 glow pulse while sound is off — border + opacity cycle.
       Single small element, negligible repaint cost. */
    animation: soundPulse 1.8s ease-in-out infinite;
  }
  @keyframes soundPulse {
    0%, 100% { opacity: 0.5; border-color: rgba(111,214,255,0.4); }
    50%      { opacity: 1;   border-color: rgba(111,214,255,1.0); }
  }
  #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: 22px; left: 160px;
    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;
  }
  #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 next to sound toggle, only visible when
     audio is playing. Positioned to clear the sound button which can be
     up to ~140px wide depending on its current label. */
  #stop-audio-btn {
    position: fixed;
    top: 18px;
    left: 160px;
    z-index: var(--z-toolbar);
    width: 30px;
    height: 30px;
    background: rgba(10,18,30,0.55);
    border: 1px solid rgba(255,140,140,0.5);
    color: rgba(255,180,180,0.85);
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    line-height: 12px;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s ease, background 0.3s, border-color 0.3s;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  #stop-audio-btn.show {
    opacity: 0.9;
    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;
    bottom: 32px; left: 0; right: 0;
    text-align: center;
    z-index: var(--z-hint);
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.32em;
    text-transform: uppercase;
    color: var(--ink);
    opacity: 0;
    transition: opacity 1.4s ease;
    pointer-events: none;
  }
  #hint .arrow {
    display: block;
    margin-top: 10px;
    font-size: 14px;
    animation: bob 2.4s infinite ease-in-out;
  }
  @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 specifically — the sig "52 Hz" box reveals right after the
     prose "The signal centers near" lands, so the prose, the box,
     and the autoplayed sound all happen in the right order.
     First paragraph (~41 words) finishes around 5.2s; "near" lands
     around 6.7s; the box appears at 7s — right when the eye reaches it. */
  /* sig delay now computed by JS from word index — no hardcode needed */
  /* the paragraphs that come AFTER the sig box must wait for it */
  #c3 .hold-after-sig .bw {
    transition-delay: calc(var(--i, 0) * 0.10s + 6s) !important;
  }
  #c3 .hold-after-sig-extra .bw {
    transition-delay: calc(var(--i, 0) * 0.10s + 8.5s) !important;
  }
  /* c7 — pullquote container needs a longer delay than default 6.5s
     because c7 has ~57 words before it (finishes ~7.7s). Pushes the
     pullquote reveal to 9s so it arrives after the prose has landed. */
  .chapter#c7 .pullquote {
    transition: opacity 1.8s ease 9s, transform 1.8s ease 9s,
                border-color 1.4s ease 9s, background 1.4s ease 9s,
                box-shadow 1.4s ease 9s;
  }
  /* c7 — "They settle… anomaly…" waits for the cite to be read.
     Pullquote body: visible ~9-10.8s. Quote marks: 11s. Cite: 12s.
     Post-pullquote text: 15s (3s after cite, time to read it). */
  #c7 .hold-after-pullquote .bw {
    transition-delay: calc(var(--i, 0) * 0.12s + 15s) !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);
    /* hide the box outline AND background so the empty layout slot
       doesn't visually telegraph the cyan element before its reveal */
    border-color: transparent;
    background: transparent;
    box-shadow: none;
    transition: opacity 1.8s ease 6.5s, transform 1.8s ease 6.5s,
                border-color 1.4s ease 6.5s, background 1.4s ease 6.5s,
                box-shadow 1.4s ease 6.5s;
  }
  .chapter.visible .media,
  .chapter.visible .pullquote,
  .chapter.visible .specimen-card,
  .chapter.visible .whale-chart,
  .chapter.visible .sequence-cue,
  .chapter.visible .year-counter,
  .chapter.visible .drift-graphic,
  .chapter.visible .lead-line,
  .chapter.visible .people {
    opacity: 1;
    transform: translateY(0);
    border-color: rgba(111,214,255,0.28);
    background: rgba(4,10,22,0.6);
  }
  /* ----------------------------------------------------------
     Per-chapter delay overrides — chapter 10 has more prose than
     the default 6.5s synced reveal anticipates, so its cyan boxes
     would appear before the surrounding text finishes kineticizing.
     Word-count math:
       drift-graphic comes after ~84 words → fade in at ~11s
       lead-line comes after ~127 words   → fade in at ~17s
       people cards come right after lead → fade in at ~19s
     The .chapter#cN selector wins specificity (one ID + two classes
     vs. the original two-class selector).
     ---------------------------------------------------------- */
  /* c5: ~85 words before the first cyan box. Three boxes cascade:
     people (researchers) at 11s, specimen card at 14s, whale chart at 17s.
     Each waits for the reader to process the one above it. */
  .chapter#c5 .people {
    transition: opacity 1.8s ease 11s, transform 1.8s ease 11s,
                border-color 1.4s ease 11s, background 1.4s ease 11s,
                box-shadow 1.4s ease 11s;
  }
  .chapter#c5 .specimen-card {
    transition: opacity 1.8s ease 14s, transform 1.8s ease 14s,
                border-color 1.4s ease 14s, background 1.4s ease 14s,
                box-shadow 1.4s ease 14s;
  }
  .chapter#c5 .whale-chart {
    transition: opacity 1.8s ease 17s, transform 1.8s ease 17s,
                border-color 1.4s ease 17s, background 1.4s ease 17s,
                box-shadow 1.4s ease 17s;
  }
  /* c8: ~95 words before the migration map */
  .chapter#c8 .media {
    transition: opacity 1.8s ease 12s, transform 1.8s ease 12s,
                border-color 1.4s ease 12s, background 1.4s ease 12s,
                box-shadow 1.4s ease 12s;
  }
  /* c10: word-count math unchanged */
  .chapter#c10 .drift-graphic {
    transition: opacity 1.8s ease 11s, transform 1.8s ease 11s,
                border-color 1.4s ease 11s, background 1.4s ease 11s,
                box-shadow 1.4s ease 11s;
  }
  .chapter#c10 .lead-line {
    transition: opacity 1.8s ease 17s, transform 1.8s ease 17s,
                border-color 1.4s ease 17s, background 1.4s ease 17s,
                box-shadow 1.4s ease 17s;
  }
  .chapter#c10 .people {
    transition: opacity 1.8s ease 19s, transform 1.8s ease 19s,
                border-color 1.4s ease 19s, background 1.4s ease 19s,
                box-shadow 1.4s ease 19s;
  }
  /* 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 8.5s, transform 2s ease 8.5s,
                border-color 1.6s ease 8.5s, background 1.6s ease 8.5s;
  }
  .chapter.visible .listening-station {
    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% { box-shadow: 0 0 0 0 rgba(111,214,255,0); }
    50%      { box-shadow: 0 0 36px 0 rgba(111,214,255,0.22); }
  }
  .listening-station.attention {
    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);
  }

  /* ----------------------------------------------------------
     FINAL CALL MOMENT — c11 closer (8 second isolated whale 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;
  }
  .final-call.show { opacity: 1; transform: none; }
  #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: 200px;
    z-index: var(--z-toolbar);
    background: rgba(10,18,30,0.55);
    border: 1px solid rgba(111,214,255,0.35);
    color: var(--ink);
    padding: 9px 14px;
    font-family: "SF Mono", Menlo, monospace;
    font-size: 13px;
    line-height: 12px;
    height: 30px;
    cursor: pointer;
    transition: background 0.4s, border-color 0.4s;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  #chapter-menu-btn:hover { background: rgba(111,214,255,0.15); border-color: var(--accent); }
  #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: 14px; left: 130px; 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; }
  /* 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;
  }
  /* sound call-to-action — sits between invite and begin button,
     bright accent color, directs the reader to the fixed 🎧 button
     at the viewport bottom (not to Begin descent) */
  .sound-cta {
    font-family: "SF Mono", Menlo, monospace;
    font-size: 11px;
    letter-spacing: 0.22em;
    text-transform: uppercase;
    color: var(--accent);
    text-shadow: 0 0 12px rgba(111,214,255,0.5);
    margin-top: 24px;
    max-width: 420px;
    opacity: 0;
    animation: introFade 1.6s 3.8s forwards;
  }
  body .sound-cta.hidden { display: none; }

  #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; }

  /* progress line on left edge */
  #progress {
    position: fixed;
    top: 0; left: 0;
    width: 2px;
    height: 0%;
    background: linear-gradient(180deg, rgba(111,214,255,0.6), rgba(111,214,255,0.12));
    z-index: var(--z-hint);
    pointer-events: none;
  }

  /* ----------------------------------------------------------
     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;
    /* The marks fade in after the pullquote body is visible. Container
       fades in at 9s (c7 override), body settles by ~10.8s. Marks at 11s. */
    transition: opacity 1.4s ease 11s;
  }
  .pullquote::before { margin-bottom: 48px; }
  .pullquote::after  { margin-top: 56px; }
  .chapter.visible .pullquote::before,
  .chapter.visible .pullquote::after {
    opacity: 0.45;
  }
  .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;
    /* After the quote marks (11s) — cite at 12s. */
    opacity: 0;
    transition: opacity 1.4s ease 12s;
  }
  .chapter.visible .pullquote cite {
    opacity: 1;
  }
  /* even slower kinetic stagger for pull quotes — most weight per word */
  .pullquote .bw {
    transition-delay: calc(var(--i, 0) * 0.38s + 0.6s);
    transition-duration: 2.0s;
  }

  /* ----------------------------------------------------------
     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: rgba(1,4,10,0.95);
    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) */
  }

