/* ============================================================
   phase-lock.css -- GENERATED by site/build-pages.py. Do not hand-edit:
   base layer is extracted from preview.html; design layer below is the
   Phase B port from page-format-playground.html (template in the script).
   ============================================================ */

  /* Self-hosted fonts (Google's latin-subset variable slices; served same-origin
     so first paint is already in the real faces -- no network FOUT). */
  @font-face { font-family: 'EB Garamond'; font-style: normal; font-weight: 400 800; font-display: swap;
    src: url('fonts/eb-garamond-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
  @font-face { font-family: 'EB Garamond'; font-style: italic; font-weight: 400 800; font-display: swap;
    src: url('fonts/eb-garamond-italic-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
  @font-face { font-family: 'Source Serif 4'; font-style: normal; font-weight: 400 700; font-display: swap;
    src: url('fonts/source-serif-4-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
  @font-face { font-family: 'Source Serif 4'; font-style: italic; font-weight: 400 700; font-display: swap;
    src: url('fonts/source-serif-4-italic-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
  @font-face { font-family: 'Inter'; font-style: normal; font-weight: 400 700; font-display: swap;
    src: url('fonts/inter-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
  @font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400 700; font-display: swap;
    src: url('fonts/jetbrains-mono-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

  /* Metric-matched Georgia fallbacks: if the woff2 ever lags, the swap keeps
     (near-)identical line breaks. Overrides computed against Georgia.ttf. */
  @font-face { font-family: 'Source Serif 4 Fallback'; src: local('Georgia');
    size-adjust: 101.70%; ascent-override: 101.87%; descent-override: 32.94%; line-gap-override: 0%; }
  @font-face { font-family: 'EB Garamond Fallback'; src: local('Georgia');
    size-adjust: 85.73%; ascent-override: 117.47%; descent-override: 34.76%; line-gap-override: 0%; }

  :root {
    --bg: #0e0e0e; --bg-2: #181818; --line: #2a2a2a; --text: #e8e8e8;
    --muted: #888; --dim: #555; --accent: #ffb86b; --accent-2: #6bdcff;
    --green: #58c466; --orange: #ffa84a; --red: #ef6f6f; --purple: #b48bff;
    --serif: "Charter", "Iowan Old Style", "Georgia", serif;
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  }
  * { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: var(--serif); }

  /* iOS paints a translucent black rectangle over any tapped element carrying a click
     handler or cursor:pointer. Every interactive figure builds its tap targets as
     transparent SVG rects at runtime, so that highlight lands as a grey box the size of
     the hotspot, a beat before the card it opens. Suppressed at each figure container:
     the property inherits, so it reaches the injected children (the same route the
     one-off #br-svg rule already takes). Scoped to the figures on purpose; body text,
     links and page chrome keep their wash, re-tinted brass just below. */
  .visual-frame, .cine-plate, .brain-loop, .cine-lightbox, #p2r { -webkit-tap-highlight-color: transparent; }
  /* Everywhere else the native tap flash stays, but in-palette: soft brass instead of
     the system grey (the one hue outside the palette). Inherits from the root; the
     transparent figure rules above still win locally. Alpha 0.15 rather than
     --accent-soft's 0.12 because brass-on-cream has less luminance contrast than
     grey; raise toward 0.18 if it reads too faint on device. */
  html { -webkit-tap-highlight-color: rgba(184, 134, 11, 0.15); }
  body { --page-pad: 24px; padding: 60px var(--page-pad) 80px; }
  .essay { max-width: 720px; margin: 0 auto; }
  /* Baseline: every image is responsive by default, so a bare <img> (no
     wrapper class) can never exceed the column and overflow on mobile. */
  .essay img { max-width: 100%; height: auto; }
  .essay-header { margin-bottom: 40px; }
  h1.title { font-size: 38px; font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; margin: 0 0 10px; }
  p.subtitle { font-size: 17px; font-style: italic; color: var(--muted); margin: 0 0 24px; line-height: 1.5; }
  h2 .num { color: var(--dim); font-family: var(--mono); font-size: 14px; margin-right: 12px; font-weight: 400; }
  p { font-size: 16.5px; line-height: 1.7; margin: 0 0 18px; }
  p em { color: var(--text); }
  a { color: var(--accent-2); text-decoration: underline; text-decoration-color: rgba(107, 220, 255, 0.4); text-underline-offset: 2px; }
  a:hover { text-decoration-color: var(--accent-2); }
  blockquote { margin: 22px 0; padding: 14px 20px; border-left: 3px solid var(--accent); background: rgba(255, 184, 107, 0.05); color: var(--text); font-size: 15.5px; line-height: 1.65; font-style: italic; }
  blockquote em { color: var(--text); font-style: italic; font-weight: 400; }
  .visual-frame { margin: 28px 0; background: var(--bg-2); border: 1px solid var(--line); border-radius: 8px; padding: 22px 18px 18px; }
  .visual-frame .vis-label { font-family: var(--sans); font-size: 10px; text-transform: uppercase; letter-spacing: 0.14em; color: var(--accent); font-weight: 600; margin-bottom: 12px; display: block; }
  .visual-frame .vis-caption { font-family: var(--sans); font-size: 12px; color: var(--muted); margin-top: 12px; line-height: 1.5; text-align: center; }
  .visual-frame svg { display: block; margin: 0 auto; max-width: 100%; height: auto; }
  hr { border: none; border-top: 1px solid var(--line); margin: 50px 0; }
  ul { padding-left: 24px; line-height: 1.7; }
  ul li { margin-bottom: 6px; font-size: 16px; }
  .footnotes { margin-top: 40px; padding-top: 20px; border-top: 1px dashed var(--line); font-size: 13px; line-height: 1.6; color: var(--muted); font-family: var(--sans); }
  .footnotes p { font-size: 13px; margin-bottom: 10px; }

  /* ============================================================================
     A++ (Voyage Bridge Production) styling override; applied at end of cascade
     Locked to satpugnet.com production tokens. --accent-2 and --modality-internet
     mapped to the site --accent-blue (#4A6B8A) so prose and diagrams share one hue.
     ============================================================================ */
  :root {
    --bg: #FAF7F2;
    --bg-2: #FFFFFF;
    --line: rgba(44, 36, 22, 0.1);
    --text: #2C2416;
    --text-2: #4A3D2C;
    --muted: #7B6B58;
    --dim: rgba(44, 36, 22, 0.4);
    --accent: #B8860B;
    --accent-2: #3E5A76;
    --accent-soft: rgba(184, 134, 11, 0.12);
    --accent-strong: #8B6914;
    --green: #6B7B5C;
    --orange: #A0522D;
    --red: #8B3A3A;
    --purple: #6B5C8A;
    --serif: 'Source Serif 4', 'Source Serif 4 Fallback', 'Iowan Old Style', Georgia, serif;
    --serif-display: 'EB Garamond', 'EB Garamond Fallback', Georgia, serif;
    --sans: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
    --mono: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
    /* Shared motion vocabulary so every animation feels like one system */
    --ease-draw: cubic-bezier(0.22, 1, 0.36, 1);   /* expo-out: draws, fades, rises */
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* gentle overshoot: pops, lands */
    --ease-bar: cubic-bezier(0.25, 0.8, 0.4, 1);   /* smooth grow: bars, scales */
  }

  body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--serif);
  }

  .essay { max-width: 720px; }

  h1.title {
    font-family: var(--serif-display);
    font-weight: 500;
    color: var(--text);
    letter-spacing: -0.015em;
  }
  p.subtitle { color: var(--muted); font-family: var(--serif); font-style: italic; }
  /* Masthead one-liner (playground pick 09, 21-08-2026): brass small-caps name,
     quiet date after a middot. The two divs stay separate in the markup (the
     site build swaps the .last-updated line for its popover dateline); they
     read as one line because both are inline-block. */
  .byline { display: inline-block; color: var(--accent-strong); font-family: var(--sans); font-size: 11.5px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; }

  p { color: var(--text-2); font-family: var(--serif); }
  p strong { color: var(--text); font-weight: 700; }
  p em { color: var(--text); font-style: italic; }

  a {
    color: var(--accent-2);
    text-decoration-color: rgba(74, 107, 138, 0.4);
  }
  a:hover { color: var(--accent); text-decoration-color: var(--accent); }

  h2 {
    font-family: var(--serif-display);
    font-weight: 500;
    font-size: 24px;
    line-height: 1.2;
    letter-spacing: -0.01em;
    margin: 50px 0 18px;
    color: var(--text);
    border-bottom: 1px solid var(--line);
    padding-bottom: 12px;
    position: relative;
  }
  h2 .num { color: var(--dim); font-family: var(--sans); font-weight: 500; }
  h2::after {
    content: "";
    position: absolute;
    bottom: -1px;
    left: 0;
    width: 56px;
    height: 2px;
    background: var(--accent);
  }

  .essay h3 {
    font-family: var(--serif-display);
    font-style: normal;
    font-weight: 600;
    font-size: 20px;
    line-height: 1.3;
    letter-spacing: -0.005em;
    margin: 32px 0 12px;
    color: var(--accent-strong);
  }

  /* Regular blockquote = quiet aside. Muted grey border, transparent bg.
     For asides like "A bit of context", "A quick primer". */
  blockquote {
    background: transparent;
    border-left: 3px solid rgba(184, 134, 11, 0.35);
    color: var(--text-2);
    font-style: normal;
    padding: 14px 22px;
    border-radius: 0;
    font-family: var(--serif-display);
    font-size: 18px;
    line-height: 1.55;
  }
  blockquote:not(.thesis) p { font-family: var(--serif-display); font-size: 18px; line-height: 1.55; }
  blockquote:not(.thesis) p:last-child { margin-bottom: 0; }
  blockquote:not(.thesis)::before {
    content: "Sidenote";
    display: block;
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    font-style: normal;
    color: var(--accent);
    font-weight: 600;
    margin-bottom: 8px;
  }
  blockquote em { color: var(--text); font-style: italic; font-weight: 400; }
  /* Bold labels inside regular blockquotes should NOT also be italic.
     Bold + italic + serif reads heavy; non-italic bold pops cleanly against
     the surrounding italic body. */
  blockquote:not(.thesis) strong {
    font-style: normal;
    color: var(--text);
    font-weight: 700;
  }

  /* Thesis blockquote = the central claim. Brass chrome, prominent.
     This is the one that gets the accent treatment, not regular blockquotes. */
  blockquote.thesis {
    background: var(--accent-soft, rgba(184, 134, 11, 0.12));
    border-left: 3px solid var(--accent);
    color: var(--text);
    font-style: italic;
    padding: 18px 24px;
    border-radius: 0 4px 4px 0;
    font-family: var(--serif);
    font-size: 17.5px;
    line-height: 1.6;
  }
  blockquote.thesis strong {
    color: var(--accent);
    font-style: normal;
    font-weight: 600;
  }
  blockquote.thesis strong.phrase {
    color: var(--text);
    font-style: italic;
    font-weight: 700;
  }
  blockquote.thesis em { color: var(--text); font-weight: 700; }
  blockquote.thesis sup a {
    color: var(--accent);
    text-decoration: none;
    font-weight: 600;
  }
  blockquote.thesis p {
    margin: 0 0 14px;
    color: var(--text);
    font-family: var(--serif);
    font-size: 17.5px;
    line-height: 1.6;
  }
  blockquote.thesis p:last-child { margin-bottom: 0; }

  .pull-quote {
    color: var(--text);
    font-style: italic;
    font-weight: 400;
    font-family: var(--serif-display);
    font-size: 28px;
    line-height: 1.4;
    letter-spacing: -0.01em;
    text-align: center;
    max-width: 580px;
    margin: 36px auto;
  }
  /* Pull quotes carry a brass marker swipe under the text (per-line via clone). */
  .pull-quote .mk {
    background: linear-gradient(to top, rgba(184, 134, 11, 0.32) 0.30em, transparent 0.30em);
    -webkit-box-decoration-break: clone;
    box-decoration-break: clone;
    padding: 0 2px;
  }

  /* Figures = white cards on cream */
  .visual-frame {
    background: var(--bg-2) !important;
    border: 1px solid var(--line) !important;
    border-radius: 8px !important;
    box-shadow: 0 3px 8px rgba(44, 36, 22, 0.06) !important;
    padding: 24px 22px 20px !important;
    margin: 32px 0;
  }

  /* ---- §10 Decode-it-yourself: interactive on screen, static two-column frame in print / no-JS ---- */
  .decode-interactive { display: none; }
  .decode-print { display: block; }
  html.js-decode .decode-interactive { display: block; }
  html.js-decode .decode-print { display: none; }
  @media print {
    html.js-decode .decode-interactive, .decode-interactive { display: none !important; }
    html.js-decode .decode-print, .decode-print { display: block !important; }
    .stadium-readout { display: block !important; }
    .stadium-hint, .stadium-hybrid .pin .tip { display: none !important; }
  }
  .decode-figure .dw-label { font-family: var(--sans); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted); font-weight: 600; margin-bottom: 6px; }
  .decode-figure .dw-label-2 { margin-top: 16px; }
  .decode-figure .dw-heard { font-family: var(--serif); font-size: 18px; line-height: 1.5; color: var(--text); padding: 11px 15px; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; }
  .decode-figure .dw-out { font-family: var(--serif); font-size: 18px; line-height: 1.5; padding: 11px 15px; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; min-height: 56px; }
  .decode-figure .dw-e { color: var(--accent); }
  .decode-figure .dw-g { color: var(--muted); }
  .decode-figure .dw-x { color: var(--red); opacity: 0.9; --tok-op: 0.9; }
  @media (prefers-reduced-motion: no-preference) { .decode-figure .dw-out span { transition: color 0.35s ease; } }
  /* Entrance for the whole block, armed/played like .anim-seq: .dw-armed holds
     every piece at zero, .dw-in releases them on the delays JS stamps inline.
     Three motions, by role: eyebrow labels, the control and the legend RISE
     (they are furniture arriving); the two sentence boxes FADE without moving,
     so the lines that land inside them never shift; the words themselves fade
     one at a time. JS drops both classes the moment the reader takes the
     slider. */
  .decode-figure .dw-armed .dw-label,
  .decode-figure .dw-armed .dw-ctl,
  .decode-figure .dw-armed .dw-legend,
  .decode-figure .dw-armed .dw-heard,
  .decode-figure .dw-armed .dw-out,
  .decode-figure .dw-armed .dw-heard span,
  .decode-figure .dw-armed .dw-out span { opacity: 0; }
  .decode-figure .dw-armed.dw-in .dw-label,
  .decode-figure .dw-armed.dw-in .dw-ctl,
  .decode-figure .dw-armed.dw-in .dw-legend { animation: dwRise 0.42s cubic-bezier(.22,.61,.36,1) both; }
  .decode-figure .dw-armed.dw-in .dw-heard,
  .decode-figure .dw-armed.dw-in .dw-out { animation: dwFade 0.34s ease both; }
  .decode-figure .dw-armed.dw-in .dw-heard span,
  .decode-figure .dw-armed.dw-in .dw-out span { animation: dwSeq 0.34s ease forwards; }
  @keyframes dwRise { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
  @keyframes dwFade { from { opacity: 0; } to { opacity: 1; } }
  /* Settle on the token's own opacity, not a flat 1: error tokens are drawn at
     0.9 (.dw-x above), so a `to { opacity: 1 }` would leave them a shade louder
     after the entrance than the same sentence painted by dragging the slider. */
  @keyframes dwSeq { from { opacity: 0; } to { opacity: var(--tok-op, 1); } }
  .decode-figure .dw-ctl { margin-top: 16px; }
  .decode-figure .dw-ctl label { display: block; font-family: var(--sans); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); font-weight: 600; margin-bottom: 8px; }
  .decode-figure .dw-hrs { color: var(--accent); font-weight: 700; text-transform: none; letter-spacing: 0; }
  .decode-figure input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 4px; border-radius: 3px; background: rgba(44, 36, 22, 0.15); outline: none; cursor: pointer; }
  .decode-figure input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 17px; height: 17px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg-2); box-shadow: 0 0 0 1px rgba(44, 36, 22, 0.2); cursor: pointer; }
  .decode-figure input[type=range]::-moz-range-thumb { width: 17px; height: 17px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg-2); cursor: pointer; }
  .decode-figure .dw-legend { font-family: var(--sans); font-size: 12px; color: var(--muted); margin-top: 12px; text-align: center; }
  .decode-figure .dw-wm { margin-top: 7px; text-align: right; font-family: var(--mono); font-size: 8.5px;
    color: var(--muted); opacity: 0.45; pointer-events: none; user-select: none; }
  .decode-figure .pdf-cols { display: flex; gap: 14px; margin-top: 12px; }
  .decode-figure .pdf-col { flex: 1; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 11px 14px; }
  .decode-figure .pdf-col h4 { font-family: var(--sans); font-size: 10px; letter-spacing: 0.07em; text-transform: uppercase; color: var(--muted); margin: 0 0 8px; font-weight: 700; }
  .decode-figure .pdf-col p { font-family: var(--serif); font-size: 15px; line-height: 1.5; margin: 0; color: var(--text); }
  @media (max-width: 640px) { .decode-figure .pdf-cols { flex-direction: column; } }

  /* ---- §07 Brain's Scaling Law, The Receipts: stepper on screen, one static plate
     per receipt in print / no-JS (same contract as js-decode / js-reread / js-signalnoise) ---- */
  .rcp-interactive { display: none; }
  .rcp-print { display: block; }
  html.js-receipts .rcp-interactive { display: block; }
  html.js-receipts .rcp-print { display: none; }
  @media print {
    html.js-receipts .rcp-interactive, .rcp-interactive { display: none !important; }
    html.js-receipts .rcp-print, .rcp-print { display: block !important; }
  }
  /* Bleed past the frame padding to the full column: these are dense multi-panel
     paper figures and every pixel of width is legibility. */
  .rcp-stage { position: relative; margin: 0 -22px; }
  /* margin:0 is load-bearing - .rcp-slide is a <figure>, and the UA default
     figure{margin:1em 40px} would offset an out-of-flow slide 40px right while
     width:100% keeps it full width, hanging it off the right edge. */
  .rcp-slide { position: absolute; top: 50%; left: 0; width: 100%; margin: 0;
    transform: translateY(-50%); display: flex; flex-direction: column; align-items: center;
    visibility: hidden; opacity: 0; }
  .rcp-slide.on { visibility: visible; opacity: 1; }
  @media (prefers-reduced-motion: no-preference) { .rcp-slide { transition: opacity 0.22s ease; } }
  /* 42px gutter per side clears the 30px arrow with room either side of it, so no
     control ever lands on chart content. max-height caps the near-square receipts. */
  .rcp-stage img.essay-img { width: auto; height: auto; max-width: calc(100% - 84px);
    max-height: 350px; object-fit: contain; border-radius: 4px; }
  .rcp-sub { font-family: var(--sans); font-size: 11.5px; color: var(--muted);
    margin-top: 10px; text-align: center; line-height: 1.45; padding: 0 22px; }
  .rcp-src { font-style: italic; color: var(--dim); }
  .rcp-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
    width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--line);
    background: var(--bg-2); color: var(--accent); font-size: 16px; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center; box-shadow: 0 1px 3px rgba(44, 36, 22, 0.08); }
  .rcp-nav:hover { border-color: var(--accent); box-shadow: 0 2px 6px rgba(44, 36, 22, 0.14); }
  .rcp-prev { left: 5px; } .rcp-next { right: 5px; }
  .rcp-dots { display: flex; gap: 6px; justify-content: center; margin-top: 12px; }
  .rcp-dot { width: 7px; height: 7px; padding: 0; border: 0; border-radius: 50%;
    background: var(--dim); cursor: pointer; }
  .rcp-dot.on { background: var(--accent); transform: scale(1.2); }
  @media (max-width: 640px) {
    .rcp-stage { margin: 0 -14px; }
    .rcp-stage img.essay-img { max-width: calc(100% - 68px); max-height: none; }
    .rcp-sub { padding: 0 14px; font-size: 11px; }
  }

  /* ---- §23 One recording, re-read over time: interactive slider on screen,
     static 3-year panel in print / no-JS (mirrors the §10 decode-figure pattern) ---- */
  .reread-interactive { display: none; }
  .reread-print { display: block; }
  html.js-reread .reread-interactive { display: block; }
  html.js-reread .reread-print { display: none; }
  @media print {
    html.js-reread .reread-interactive, .reread-interactive { display: none !important; }
    html.js-reread .reread-print, .reread-print { display: block !important; }
  }
  .reread-print .pdf-cols { display: flex; gap: 12px; margin-top: 14px; align-items: stretch; }
  .reread-print .pdf-col { flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 11px 12px; }
  .reread-print .pdf-col h4 { font-family: var(--sans); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin: 0 0 10px; font-weight: 700; }
  .reread-print .rr-row { margin: 0 0 7px; }
  .reread-print .rr-row.off { opacity: 0.38; }
  .reread-print .rr-name { font-family: var(--sans); font-size: 12px; font-weight: 600; color: var(--text); line-height: 1.3; }
  .reread-print .rr-row.off .rr-name { color: var(--muted); font-weight: 500; }
  .reread-print .rr-sub { font-family: var(--serif); font-size: 11px; font-style: italic; color: var(--muted); line-height: 1.35; }
  .reread-print .rr-sub b { font-style: normal; font-family: ui-monospace, monospace; font-size: 10.5px; font-weight: 700; }
  .reread-print .rr-punch { font-family: var(--sans); font-size: 10.5px; color: var(--text); line-height: 1.5; margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line); }
  @media (max-width: 640px) { .reread-print .pdf-cols { flex-direction: column; } }
  /* Watermark. The interactive panel and the print panel are separate subtrees with only
     one of them displayed, so each carries its own copy of the source line rather than
     sharing one placed above the caption (the pattern the decode figure uses, which works
     there because both of its variants run the full frame width; here the interactive one
     is capped at 560px and centred, so a shared line would sit off its right edge). Until
     19-08-2026 the line lived only inside .reread-interactive, which left the PDF, where
     @media print forces .reread-print, as the one figure in the essay carrying none. */
  .reread-wm { margin-top: 12px; text-align: right; font-family: var(--mono); font-size: 8.5px;
    color: var(--muted); opacity: 0.45; pointer-events: none; user-select: none; }

  /* ---- Appendix E glossary: print-only (the web keeps its hover tooltips), compact
     two-column run-in definition list so it adds well under a page. NB: #appendix-e is
     the <h2> heading and .glossary is its FOLLOWING SIBLING <dl>, not a child, so hide
     both on screen and target .glossary directly. !important beats the print
     pipeline's density sizing (now the structural .pdf-apx wrapper in
     pdf/make-spike-excerpt.py; formerly a [id^="appendix"] ~ * sibling rule that
     silently stopped matching when Paged.js split the dl across a page, which is
     why the glossary once printed at two sizes). ---- */
  @media screen { #appendix-e, .glossary { display: none !important; } }
  /* Match the other appendices' text: same size (via the print pipeline's
     .pdf-apx density sizing = 13px compact / 13.5px comfortable) and body serif, run-in as
     "Term: definition" like Appendix D. Two columns keeps the 28 entries to ~a page. */
  .glossary { columns: 2; column-gap: 26px; margin: 10px 0 0; }
  .glossary .g-entry { break-inside: avoid; margin: 0 0 7px !important; line-height: 1.5 !important; }
  .glossary .g-entry dt { display: inline !important; font-family: var(--serif); font-weight: 700; color: var(--text); }
  .glossary .g-entry dt::after { content: ": "; }
  .glossary /* dd matches body prose (--text-2); dt above stays --text, matching the
     p strong bold-lead pattern. Found in the 20-08-2026 colour audit: the
     definitions were the essay's last prose at the dark ink. */
  .g-entry dd { display: inline !important; margin: 0 !important; font-family: var(--serif); color: var(--text-2); }

  /* ---- §4 Reading signal out of noise: interactive slider on screen, static 3-SNR panels in print / no-JS ---- */
  .sn-interactive { display: none; }
  .sn-print { display: block; }
  html.js-signalnoise .sn-interactive { display: block; }
  html.js-signalnoise .sn-print { display: none; }
  @media print {
    html.js-signalnoise .sn-interactive, .sn-interactive { display: none !important; }
    html.js-signalnoise .sn-print, .sn-print { display: block !important; }
  }
  .sn-print .sn-cols { display: flex; gap: 12px; margin-top: 4px; align-items: flex-start; }
  .sn-print .sn-col { flex: 1; min-width: 0; text-align: center; }
  .sn-print .sn-col h4 { font-family: var(--sans); font-size: 9.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); margin: 0 0 6px; font-weight: 700; }
  .sn-print .sn-col img { width: 100%; height: auto; display: block; border: 1px solid var(--line); border-radius: 5px; }
  @media (max-width: 640px) { .sn-print .sn-cols { flex-direction: column; } }

  /* §17 The Overtaking: cumulative reveal slider */
  #overtake .ot-controls { display: none; margin-top: 12px; flex-direction: column; align-items: center; gap: 6px; }
  html.js-overtake #overtake .ot-controls { display: flex; }
  #overtake .ot-row { display: flex; align-items: center; justify-content: center; gap: 12px; }
  /* nowrap: the slider beside it is a fixed 240px, so on a phone the label is the
     only thing that can give, and "AI overtakes" stacks into two lines. */
  #overtake .ot-hint { font-family: var(--serif); font-style: italic; font-size: 14px; color: var(--accent-strong); white-space: nowrap; }
  #overtake input[type=range] { -webkit-appearance: none; appearance: none; width: 240px; max-width: 62vw; height: 4px; border-radius: 3px; background: rgba(44, 36, 22, 0.15); outline: none; cursor: pointer; }
  #overtake input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 17px; height: 17px; border-radius: 50%; background: var(--accent-2); border: 2px solid var(--bg-2); box-shadow: 0 0 0 1px rgba(44, 36, 22, 0.2); cursor: pointer; }
  #overtake input[type=range]::-moz-range-thumb { width: 17px; height: 17px; border-radius: 50%; background: var(--accent-2); border: 2px solid var(--bg-2); cursor: pointer; }
  #overtake .ot-level { font-family: var(--serif); font-style: italic; font-size: 14px; color: var(--accent-strong); text-align: center; min-height: 1.1em; }
  #overtake .ot-tier { opacity: 1; }
  html.js-overtake #overtake .ot-tier { opacity: 0; }
  html.js-overtake #overtake .ot-tier.on { opacity: 1; }
  @media (prefers-reduced-motion: no-preference) { #overtake .ot-tier { transition: opacity 0.5s var(--ease-draw); } }
  /* Entrance: solid + dashed line trace in ONE clip sweep (gated on .seq-armed
     like every sq-* state, so reduced-motion / print stay static). Revealed
     tiers join a replay inside a transient .ot-replay window, stamped .ot-entr
     at fire time — anything the slider toggles afterwards reacts instantly. */
  #overtake .ot-line { transform-box: fill-box; }
  #overtake .anim-seq.seq-armed .ot-line { clip-path: inset(-6% 100% -6% 0); }
  #overtake .anim-seq.seq-armed.seq-in .ot-line { animation: ot-trace 1.5s var(--ease-draw) both; animation-delay: .55s; }
  @keyframes ot-trace { to { clip-path: inset(-6% -2% -6% 0); } }
  #overtake .ot-replay .ot-tier.ot-entr.on { animation: seqFade .5s var(--ease-draw) both; animation-delay: var(--otd, .6s); }
  @media (max-width: 640px) { #overtake .ot-hint, #overtake .ot-level { font-size: 12px; } }
  @media print { html.js-overtake #overtake .ot-tier { opacity: 1 !important; } #overtake .ot-controls { display: none !important; } }

  .visual-frame .vis-label {
    color: var(--accent);
    font-family: var(--sans);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: 10.5px;
    font-weight: 600;
    margin-bottom: 14px;
  }
  .visual-frame .vis-caption {
    text-align: center;
    font-style: italic;
    font-family: var(--serif);
    font-size: 13.5px;
    color: var(--muted);
    margin-top: 14px;
    line-height: 1.55;
  }

  /* Historical photograph plates; field-journal aesthetic */
  .historical-plate {
    margin: 36px auto;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 8px;
    box-shadow: 0 3px 8px rgba(44, 36, 22, 0.06);
    padding: 22px 22px 18px;
    max-width: 100%;
  }
  .historical-plate.full-bleed {
    max-width: 1100px;
    margin-left: calc(50% - 50vw + 8px);
    margin-right: calc(50% - 50vw + 8px);
    padding: 32px 32px 24px;
  }
  .historical-plate .plate-label {
    display: block;
    font-family: var(--sans);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: 10.5px;
    font-weight: 600;
    color: var(--accent);
    margin-bottom: 14px;

  }
  .historical-plate .plate-image {
    position: relative;
    background: var(--bg);
    border: 1px solid rgba(184, 134, 11, 0.22);
    border-radius: 4px;
    padding: 0;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .historical-plate .plate-image img {
    display: block;
    width: 100%;
    max-height: none;
    height: auto;
  }
  .historical-plate.full-bleed .plate-image img {
    max-height: none;
  }
  /* Chart plates hold white-backed figures, not sepia photographs. The brass
     hairline was a frame for photos; on a white chart inside a white card it
     draws white-on-white and reads as an arbitrary gold rectangle. Drop it and
     let the card border be the only frame. */
  .historical-plate.chart-plate .plate-image {
    background: var(--bg-2);
    border: none;
    border-radius: 0;
  }
  .historical-plate .plate-caption {
    text-align: center;
    font-style: italic;
    font-family: var(--serif);
    font-size: 14px;
    color: var(--muted);
    margin: 16px auto 0;
    max-width: none;
    line-height: 1.55;
  }

  /* Cinematic plate: edge-to-edge, black-backed still (the Aleph / black-hole treatment) */
  .cine-plate { margin: 34px 0; }
  .cine-plate img { width: 100%; height: auto; display: block; border-radius: 8px; box-shadow: 0 4px 22px rgba(44, 36, 22, 0.16); background: #000; }
  .cine-plate .vis-caption { text-align: center; font-style: italic; font-family: var(--serif); font-size: 13.5px; color: var(--muted); line-height: 1.55; margin-top: 14px; }
  /* transparent black line-art (e.g. the march-of-progress SVG): invert to white-on-black so it reads on the cinematic backing */
  .cine-plate.cine-invert img { background: #fff; filter: invert(1); }
  /* wide breakout: pull a cinematic plate past the text column, centered on the column */
  .cine-plate.wide { width: min(1080px, 100vw - 40px); max-width: none; margin-left: 50%; margin-right: 0; transform: translateX(-50%); }
  .cine-plate.wide .vis-caption { max-width: 720px; margin-left: auto; margin-right: auto; }
  @media (min-width: 1381px) {
    /* the fixed left TOC (ends near x=224px) is visible here — keep the wide breakout clear of it */
    .cine-plate.wide { width: min(1080px, 100vw - 520px); }
  }

  /* Maya story interludes -- inset scene panels (slate, matches --accent-2) */
  .story-scene{position:relative;background:linear-gradient(rgba(74,107,138,0.06),rgba(74,107,138,0.06)),var(--bg-2);border:1px solid rgba(74,107,138,0.22);border-left:3px solid var(--accent-2);border-radius:4px;padding:28px 32px;box-shadow:0 3px 16px rgba(44,36,22,0.05);margin:32px 0;}
  .essay .story-scene h3{margin-top:0;}
  .story-scene>p:last-of-type{margin-bottom:0;}
  @media (max-width:640px){.story-scene{padding:22px 20px;}}

  /* Cinematic lightbox: click any cine-plate image (or the Aleph loop) to zoom */
  .cine-plate img, .brain-loop video { cursor: pointer; }
  .cine-lightbox {
    position: fixed; inset: 0; z-index: 300;
    /* display:none when closed, NOT just opacity:0 — iOS 26 Safari samples
       invisible fixed elements for its status-bar/toolbar tinting, and this
       dark glass overlay poisoned the sample on every page that has cine
       plates (the appendix, with none, was the only page whose status bar
       rendered uniform cream). display+allow-discrete keeps the fades. */
    display: none; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
    padding: max(16px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
    background: rgba(15, 12, 8, 0.92);
    -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
    opacity: 0; pointer-events: none; transition: opacity .34s var(--ease-draw);
    transition: opacity .34s var(--ease-draw), display .34s allow-discrete;
  }
  .cine-lightbox[data-open="1"] { display: flex; opacity: 1; pointer-events: auto; }
  @starting-style {
    .cine-lightbox[data-open="1"] { opacity: 0; }
    .cine-lightbox[data-open="1"] .cine-lb-media { transform: scale(0.92); }
  }
  .cine-lightbox .cine-lb-media {
    display: block; flex: 0 0 auto; max-width: min(1680px, 96vw);
    max-height: 90vh; max-height: min(90dvh, calc(100dvh - 108px)); width: auto; height: auto; object-fit: contain;
    border-radius: 8px; box-shadow: 0 12px 60px rgba(0, 0, 0, .5); background: #000; cursor: pointer;
    transform: scale(0.92); transition: transform .42s var(--ease-draw); will-change: transform;
  }
  .cine-lightbox[data-open="1"] .cine-lb-media { transform: scale(1); }
  .cine-lightbox.cine-lb-inverted .cine-lb-media { background: #fff; filter: invert(1); }
  .cine-lightbox .cine-lb-cap {
    flex: 0 0 auto; max-width: min(720px, 92vw); margin: 0; text-align: center; font-style: italic;
    font-family: var(--serif); font-size: 14px; line-height: 1.55; color: rgba(250, 247, 242, 0.82);
    /* 68px was three lines, which silently scroll-clipped the longest captions on
       narrow phones (where 92vw wraps them to five). Tablets and desktop never
       reach this cap at all, so raising it only affects the phones that need it. */
    max-height: 22vh; overflow-y: auto;
  }
  .cine-lightbox .cine-lb-cap:empty { display: none; }
  .cine-lb-close {
    position: absolute; top: max(14px, env(safe-area-inset-top)); right: max(14px, env(safe-area-inset-right));
    width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
    padding: 0; border: 0; border-radius: 999px; cursor: pointer;
    background: rgba(250, 247, 242, 0.12); color: rgba(250, 247, 242, 0.92);
    font-family: var(--sans); font-size: 26px; line-height: 1;
    transition: background .16s, color .16s, transform .16s;
  }
  .cine-lb-close:hover { background: rgba(250, 247, 242, 0.24); color: #fff; }
  .cine-lb-close:active { transform: scale(0.92); }
  .cine-lb-close:focus, .cine-lb-close:focus-visible { outline: none; }
  @media (prefers-reduced-motion: reduce) {
    .cine-lightbox { transition: opacity .18s linear; }
    .cine-lightbox .cine-lb-media, .cine-lightbox[data-open="1"] .cine-lb-media { transform: none; transition: none; }
  }
  /* Short and wide: a phone in landscape, or a squat desktop window. Height is the
     scarce axis there and width is abundant, but the stacked caption reserves a
     flat 108px of height regardless, which on a 320px-tall viewport collapses a
     portrait photo to a sixth of the screen. Lay the plate and its label out side
     by side instead, so the picture gets the whole height and the caption uses the
     width that was going spare. */
  @media (max-height: 560px) and (orientation: landscape) {
    .cine-lightbox { flex-direction: row; gap: 20px; padding: 14px; }
    .cine-lightbox .cine-lb-media { max-height: calc(100dvh - 28px); flex: 0 1 auto; min-width: 0; }
    .cine-lightbox .cine-lb-cap {
      flex: 0 1 260px; min-width: 140px; max-width: 260px;
      max-height: calc(100dvh - 56px); text-align: left; font-size: 13px;
    }
  }
  @media print { .cine-lightbox { display: none !important; } }

  .footnotes {
    border-top: 1px dashed var(--line);
    color: var(--muted);
    font-family: var(--sans);
    font-size: 13px;
  }
  .footnotes p { font-size: 13px; font-family: var(--sans); }

  hr { border-top: 1px solid var(--line); }
  ul li, ol li { color: var(--text-2); font-family: var(--serif); }

  /* Inline code: ocean-blue-tinted mono */
  code, p code {
    font-family: var(--mono);
    font-size: 0.88em;
    background: rgba(74, 107, 138, 0.08);
    color: var(--accent-2);
    padding: 1px 6px;
    border-radius: 3px;
  }

  /* Recurring brass chrome (were repeated inline styles) */
  .eyebrow {
    font-size: 12.5px;
    letter-spacing: 0.1em;
    color: var(--accent);
    text-transform: uppercase;
    font-weight: 600;
    margin-bottom: 12px;
  }
  /* Outline block: web = clickable cartouches, PDF = vertical numbered list.
     The PDF render emulates print media, so @media print does the swap. */
  .outline-block { margin-top: 40px; }
  .outline-hr { display: flex; align-items: center; gap: 16px; margin: 0 0 40px; }
  .outline-hr::before, .outline-hr::after { content: ""; flex: 1; height: 1px; background: rgba(184, 134, 11, 0.28); }
  .outline-hr .eyebrow { margin: 0; font-size: 14px; }
  .outline-print { display: none; }
  @media print { .outline-screen { display: none; } .outline-print { display: block; break-before: page; } }
  /* cartouche (web) */
  .ol-cart-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
  .ol-cart { display: block; text-align: center; text-decoration: none; border-bottom: none;
    background: linear-gradient(180deg, #FBF8F3, #F4EDE1); border: 1px solid rgba(184, 134, 11, 0.3);
    border-radius: 6px; padding: 16px 14px; transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
    -webkit-tap-highlight-color: transparent; }
  .ol-cart:hover { border-color: rgba(184, 134, 11, 0.42); box-shadow: 0 2px 10px rgba(184, 134, 11, 0.13); transform: translateY(-1px); }
  /* Press inverts the hover: hover lifts the card, a press sinks it. Suppressing the iOS
     default tap wash (grey, the one hue outside the palette) leaves no feedback at all on a
     phone, so it has to be replaced rather than removed. Also cures a desktop artefact:
     without this, a click holds the card in its :hover lift while the page scrolls away. */
  .ol-cart:active { transform: translateY(0) scale(0.985); }
  .ol-cart-num { display: block; font-family: var(--mono); font-size: 10px; letter-spacing: 0.14em; color: var(--accent); text-transform: uppercase; }
  .ol-cart-title { display: block; font-family: var(--serif); font-size: 18px; font-weight: 600; color: var(--text); margin: 5px 0; }
  .ol-cart-rule { display: block; width: 26px; height: 1px; background: var(--accent); opacity: 0.5; margin: 7px auto; }
  .ol-cart-q { display: block; font-family: var(--sans); font-size: 11.5px; color: var(--text-2); line-height: 1.35; }
  @media (max-width: 560px) { .ol-cart-grid { grid-template-columns: 1fr; } }
  /* classic TOC with leader dots (PDF). Rows are ANCHORS so the PDF render emits
     click-to-jump link annotations (Sat, 08-09-2026); the .ol-toc-pg span is filled
     with the real page number post-pagination by pdf/render-paged.mjs (empty and
     harmless in a plain browser print). Question moves to its own second line so the
     number owns the right edge. */
  .ol-toc-row { display: block; color: inherit; text-decoration: none; border-bottom: none; font-family: var(--serif); font-size: 15px; padding: 12px 0; }
  .ol-toc-line1 { display: flex; align-items: baseline; }
  .ol-toc-part { color: var(--accent-strong); font-size: 13px; font-family: var(--sans); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; flex: 0 0 auto; }
  .ol-toc-title { color: var(--text); font-weight: 600; margin-left: 12px; flex: 0 0 auto; }
  .ol-toc-dots { flex: 1 1 auto; border-bottom: 1px dotted var(--muted); margin: 0 10px 5px; opacity: 0.5; }
  .ol-toc-pg { color: var(--text); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
  .ol-toc-q { display: block; color: var(--text-2); font-family: var(--sans); font-size: 12.5px; font-style: italic; margin: 3px 0 0 62px; }

  /* SVG color overrides; map old dark palette to A++ Voyage Bridge Production */
  .visual-frame svg [fill="#ffb86b"] { fill: var(--accent) !important; }
  .visual-frame svg [stroke="#ffb86b"] { stroke: var(--accent) !important; }
  .visual-frame svg [fill="#58c466"] { fill: var(--green) !important; }
  .visual-frame svg [stroke="#58c466"] { stroke: var(--green) !important; }
  .visual-frame svg [fill="#ffa84a"] { fill: var(--orange) !important; }
  .visual-frame svg [stroke="#ffa84a"] { stroke: var(--orange) !important; }
  .visual-frame svg [fill="#ef6f6f"] { fill: var(--red) !important; }
  .visual-frame svg [stroke="#ef6f6f"] { stroke: var(--red) !important; }
  .visual-frame svg [fill="#6bdcff"] { fill: var(--accent-2) !important; }
  .visual-frame svg [stroke="#6bdcff"] { stroke: var(--accent-2) !important; }
  .visual-frame svg [fill="#b48bff"] { fill: var(--purple) !important; }
  .visual-frame svg [stroke="#b48bff"] { stroke: var(--purple) !important; }
  .visual-frame svg [fill="#3a3a3a"] { fill: rgba(44, 36, 22, 0.45) !important; }
  .visual-frame svg [stroke="#3a3a3a"] { stroke: rgba(44, 36, 22, 0.45) !important; }
  .visual-frame svg [fill="#2a2a2a"] { fill: rgba(44, 36, 22, 0.25) !important; }
  .visual-frame svg [stroke="#2a2a2a"] { stroke: rgba(44, 36, 22, 0.25) !important; }
  .visual-frame svg [fill="#5a5a5a"] { fill: var(--dim) !important; }
  .visual-frame svg [stroke="#5a5a5a"] { stroke: var(--dim) !important; }
  .visual-frame svg [fill="#888"] { fill: var(--muted) !important; }
  .visual-frame svg [stroke="#888"] { stroke: var(--muted) !important; }
  .visual-frame svg [fill="#666"] { fill: var(--muted) !important; }
  .visual-frame svg [stroke="#666"] { stroke: var(--muted) !important; }
  .visual-frame svg [fill="#555"] { fill: rgba(44, 36, 22, 0.5) !important; }
  .visual-frame svg [stroke="#555"] { stroke: rgba(44, 36, 22, 0.5) !important; }
  .visual-frame svg [fill="#444"] { fill: rgba(44, 36, 22, 0.35) !important; }
  .visual-frame svg [stroke="#444"] { stroke: rgba(44, 36, 22, 0.35) !important; }
  .visual-frame svg [fill="#cccccc"] { fill: var(--text-2) !important; }
  .visual-frame svg [fill="#aaa"] { fill: #8B7B68 !important; }
  .visual-frame svg [fill="#e0e0e0"] { fill: var(--text) !important; }
  .visual-frame svg [fill="#e8e8e8"] { fill: var(--text) !important; }
  .visual-frame svg [fill="#fff"] { fill: var(--bg-2) !important; }
  .visual-frame svg [fill="#0e0e0e"] { fill: var(--bg-2) !important; }
  .visual-frame svg [fill="#181818"] { fill: var(--bg-2) !important; }
  .visual-frame svg [stroke="#0e0e0e"] { stroke: var(--bg-2) !important; }
  .visual-frame svg [stroke="#181818"] { stroke: var(--bg-2) !important; }

  /* Production-hex remaps: figure colors stamped with the palette hexes follow the
     tokens, so diagram labels track the prose by construction (same idiom as the
     legacy dark-palette remaps above). The #2C2416/#7B6B58/#8B6914 pairs are no-ops
     at current values; kept so every future token change propagates without a sweep. */
  .visual-frame svg [fill="#5C4D3C"] { fill: var(--text-2) !important; }
  .visual-frame svg [stroke="#5C4D3C"] { stroke: var(--text-2) !important; }
  .visual-frame svg [stop-color="#5C4D3C"] { stop-color: var(--text-2); }
  .visual-frame svg [fill="#4A6B8A"] { fill: var(--accent-2) !important; }
  .visual-frame svg [stroke="#4A6B8A"] { stroke: var(--accent-2) !important; }
  .visual-frame svg [stop-color="#4A6B8A"] { stop-color: var(--accent-2); }
  .visual-frame svg [fill="#7B6B58"] { fill: var(--muted) !important; }
  .visual-frame svg [stroke="#7B6B58"] { stroke: var(--muted) !important; }
  .visual-frame svg [fill="#2C2416"] { fill: var(--text) !important; }
  .visual-frame svg [stroke="#2C2416"] { stroke: var(--text) !important; }
  .visual-frame svg [fill="#8B6914"] { fill: var(--accent-strong) !important; }
  .visual-frame svg [stroke="#8B6914"] { stroke: var(--accent-strong) !important; }

  /* SVG marker arrow color sweeps */
  .visual-frame svg marker path[fill="#ffb86b"] { fill: var(--accent) !important; }
  .visual-frame svg marker path[fill="#4a4a4a"] { fill: var(--muted) !important; }

  /* ============================================================================
     Sidenotes (imported from F4); Tufte-style margin annotations
     Each footnote ref in the body gets a sidenote cloned next to it
     ============================================================================ */
  .essay { position: relative; }

  .sidenote {
    display: none;
    float: right;
    clear: right;
    width: 220px;
    margin-right: -260px;
    margin-top: 0;
    margin-bottom: 1.4em;
    font-family: var(--sans);
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--muted);
    padding-left: 12px;
    border-left: 2px solid var(--accent);
    font-style: normal;
    font-weight: 400;
    /* own stacking context, so the click-to-locate wash can sit behind the text */
    position: relative;
    z-index: 0;
  }
  .sidenote strong {
    font-weight: 700;
    color: var(--accent);
    margin-right: 3px;
  }
  .sidenote a {
    color: var(--accent-2);
    text-decoration: underline;
    text-decoration-color: rgba(74, 107, 138, 0.4);
  }
  .sidenote em { font-style: italic; color: var(--text); }

  /* Click-to-locate. Clicking a footnote numeral flashes its own sidenote, so a paragraph
     carrying three refs points at the right one of three stacked notes in the margin.
     Deliberately the same gesture the essay already uses in Appendix A, where clicking a
     node in the predictions graph scrolls to that row and flashes it: same brass, same
     1.6s curve, same hold to 35% (see #p2r .bet.flash / @keyframes p2rflash).
     The wash is a layer behind the text rather than a background on the element, so the
     brass rail stays a dead-straight 2px line. It is flush with the rail top and bottom on
     purpose: the rail is a real border-left and spans exactly the text box, so any vertical
     bleed would leave the wash taller than the rail and step out at both ends. Rounded only
     on the two free right corners for the same reason. */
  .sidenote::before {
    content: "";
    position: absolute;
    z-index: -1;
    top: 0; bottom: 0; left: 0; right: -8px;
    border-radius: 0 3px 3px 0;
    background: rgba(184, 134, 11, 0.18);
    opacity: 0;
  }
  .sidenote.sn-flash::before { animation: snflash 1.6s ease; }
  @keyframes snflash { 0%, 35% { opacity: 1; } 100% { opacity: 0; } }

  /* Show sidenotes when viewport is wide enough.
     Bottom footnotes remain canonical on narrow viewports. */
  @media (min-width: 1200px) {
    .sidenote { display: block; }
  }

  /* ============================================================================
     ENHANCEMENT LAYER (Voyage Bridge Production tokens stay locked)
     Added in one block so future maintenance is centralized.
     ============================================================================ */

  /* #01 Reading progress bar -------------------------------------------------- */
  .reading-progress {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 3px;
    background: rgba(184, 134, 11, 0.08);
    z-index: 100;
    pointer-events: none;
  }
  .reading-progress .fill {
    position: absolute;
    left: 0; top: 0;
    z-index: 1;
    height: 3px;
    width: 0%;
    background: var(--accent);
    box-shadow: 0 0 8px rgba(184, 134, 11, 0.3);
    transition: width 0.08s linear;
  }
  /* Touch devices only: the bar doubles as the iOS 26 Safari "solid top bar"
     trigger. WebKit finds the page's top fixed container by hit-testing one
     point 4px below the top edge and rejects boxes 10px tall or less
     (LocalFrameView.cpp: sampleRectMargin=4, thinBorderWidth=10), then
     extends that element's opaque background-color as a solid fill beneath
     the translucent browser chrome; without it Safari falls back to the soft
     blur that shows scrolled text through the status bar. The strip sits
     mostly above the viewport; the tracker anchors to its bottom edge so text
     meets the tracker directly, and the sliver of cream above the tracker
     merges into Safari's chrome fill. Desktop keeps the bare track above. */
  @media (hover: none) {
    .reading-progress {
      top: -8px;
      height: 13px;   /* border-box must exceed 10px for WebKit's color check */
      background: var(--bg);
    }
    .reading-progress::after {
      /* the familiar 3px brass track at the strip's bottom: nothing below it */
      content: "";
      position: absolute;
      left: 0; right: 0; bottom: 0;
      height: 3px;
      background: rgba(184, 134, 11, 0.08);
    }
    .reading-progress .fill { top: auto; bottom: 0; height: 3px; }
  }
  /* Hide section index on narrower viewports; the essay reads top-to-bottom */
  @media (max-width: 1380px) {
  }

  /* #05 Hoverable # anchors on h2 --------------------------------------------- */
  .essay h2 {
    position: relative;
    cursor: pointer;
  }
  .essay h2::before {
    content: "#";
    /* Baseline-true by construction: a zero-width inline-block participates
       in the first line box, so the # shares the heading text's baseline via
       normal line layout; text-indent paints the glyph out in the left gutter
       without occupying any space. Every box-centering recipe tried before
       (top:%/transform, abs static position) aligned boxes instead of glyph
       ink and drifted 2-8px with font size, padding, and viewport; ink-pixel
       measurement of # vs the section digits verifies this one. */
    display: inline-block;
    width: 0;
    text-indent: -26px;
    /* deliberate optical nudge below the strict shared baseline: next to the
       larger dim digits and the low brass underline, exact baseline reads a
       touch high (Sat-calibrated). Em unit so it scales with the marker. */
    vertical-align: -0.1em;
    font-family: var(--sans);
    font-size: 18px;
    font-weight: 600;
    color: var(--accent);
    opacity: 0;
    transition: opacity 0.15s ease;
    pointer-events: none;
  }
  /* Hover-capable pointers only: on iOS a tap sets sticky :hover until the next
     tap elsewhere, which pinned the # at hover strength after copying a link. */
  @media (hover: hover) {
    .essay h2:hover::before { opacity: 0.6; }
  }
  /* Touch devices: # is visible permanently (hover doesn't exist on touch). */
  @media (hover: none) {
    .essay h2::before { opacity: 0.35; }
    .essay h2:active::before { opacity: 0.8; transition: opacity 0.1s ease; }
    /* iOS paints no native tap flash on a heading (JS click listener only, not a
       link), so the copy-URL tap felt dead. Hand-rolled brass wash matching the
       global tap-highlight tint; the box-shadow spread gives the fill breathing
       room around the text without a layout shift. */
    .essay h2 { transition: background-color 0.12s ease, box-shadow 0.12s ease; }
    .essay h2:active {
      background: var(--accent-soft);
      box-shadow: 0 0 0 6px var(--accent-soft);
      border-radius: 2px;
    }
  }
  /* Click acknowledgement (set by the copy handler): the # pins at full
     opacity for ~400ms after a copy, then the ::before transition eases it
     back. Same brass hue as at rest (opacity pin only; a darker hue read as
     off-palette). Sits after the hover rule on purpose: same specificity, so
     source order lets the ack win while it lasts. */
  .essay h2.h2-copied::before { opacity: 1; transition: opacity 0.12s ease; }
  /* second phase, swapped in by the handler after the hold: carries the slow
     fade-back timing so ONLY the acknowledged path decays slowly; plain
     hover-out keeps the snappy base 0.15s */
  .essay h2.h2-copied-out::before { transition: opacity 0.3s ease; }
  /* Below 760px (narrow desktop or mobile): always show the # at low opacity,
     centered in the page gutter. It used to hang at a fixed -18px, which at
     16px phone padding pushed it half off-screen; --page-pad is the body's own
     padding var, so the marker tracks the 20px/16px breakpoints. The -4px is
     half the glyph width at 13px. */
  @media (max-width: 760px) {
    .essay h2::before { text-indent: calc(var(--page-pad) / -2 - 4px); font-size: 13px; opacity: 0.35; }
  }
  @media (max-width: 760px) and (hover: hover) {
    .essay h2:hover::before { opacity: 0.7; }
  }

  @media (max-width: 1380px) {
  }

  /* Desktop sticky index is already hidden under 1380px. When the drawer is
     opened on narrow viewports, re-show it as a full-viewport overlay. */
  @media (max-width: 1380px) {
  }

  /* Mobile readability pass.
     Long-form serif benefits from a slightly LARGER body size on mobile
     (closer to iOS Reader / Medium standards), tighter line-height to keep
     vertical scroll reasonable, and proportionally scaled display elements.
     Body padding stays generous to keep the measure ~45-55 chars/line. */
  @media (max-width: 760px) {
    body { --page-pad: 20px; padding: 56px var(--page-pad) 72px; }
    p { font-size: 17px; line-height: 1.65; }
    .essay h2 { font-size: 22px !important; margin: 42px 0 16px !important; }
    .essay h3 { font-size: 18.5px !important; }
    h1.title { font-size: 34px; }
    p.subtitle { font-size: 17px; }
    .byline { font-size: 11px; }
    .last-updated { font-size: 11.5px; }
    blockquote { font-size: 16.5px; line-height: 1.6; padding: 12px 18px; }
    blockquote.thesis { font-size: 17px !important; padding: 16px 20px !important; }
    blockquote.thesis p { font-size: 17px !important; line-height: 1.6 !important; }
    .pull-quote { font-size: 22px !important; line-height: 1.35 !important; margin: 30px auto !important; }
    .visual-frame { padding: 18px 14px 14px !important; margin: 24px 0; }
    /* chart plates match the frame cards' tighter phone padding (22px is desktop air) */
    .historical-plate.chart-plate { padding: 18px 14px 14px; }
    .visual-frame .vis-caption { font-size: 12.5px; }
    .footnotes p { font-size: 13.5px; line-height: 1.6; }
    .fn-popover { font-size: 14px; line-height: 1.6; }
    ul li, ol li { font-size: 16px; line-height: 1.6; }
  }
  @media (max-width: 420px) {
    body { --page-pad: 16px; padding: 56px var(--page-pad) 64px; }
    h1.title { font-size: 28px; line-height: 1.15; }
    p.subtitle { font-size: 16.5px; }
    .essay h2 { font-size: 21px !important; }
    p { font-size: 17px; }
  }
  /* Small phones: every figure card bleeds to 8px from the screen edge
     (validated on the biological baseline; width is scarce below 360). */
  @media (max-width: 360px) {
    .visual-frame { margin: 24px calc(8px - var(--page-pad)); }
  }

  /* #20 Footnote popover (companion to sidenotes; appears on narrow/medium
     viewports where the right-margin sidenote is hidden). Hover on desktop,
     tap-toggle on touch. Single shared element positioned by JS.
     Document-anchored (absolute; the JS folds scrollY into the coords) so the
     compositor scrolls it with its reference natively. It used to be
     position:fixed chased from a scroll listener, but iOS delivers
     momentum-scroll events after paint, so the note visibly trailed the
     moving text in chunks and then snapped back. */
  .fn-popover {
    position: absolute;
    /* display:none when hidden, not just opacity:0: iOS 26 Safari reads
       background-color off invisible overlay elements when tinting its bars;
       only display:none escapes the sampler. */
    display: none;
    opacity: 0;
    pointer-events: none;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent);
    border-radius: 6px;
    box-shadow: 0 10px 30px rgba(44, 36, 22, 0.18);
    padding: 14px 18px;
    font-family: var(--sans);
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-2);
    max-width: 360px;
    width: max-content;
    z-index: 60;
    /* No entrance animation (31-08-2026). The four sibling overlays, .paper-card,
       .ref-card, the diagram card and the .acronym tooltip, all appear instantly;
       this was the only animated one of the five, and it is by far the most
       triggered (71 footnotes against 49 paper links, 36 reference links, 30
       acronyms). Do not reintroduce a transition here without moving the other
       four with it. */
  }
  .fn-popover.visible {
    display: block;
    opacity: 1;
    pointer-events: auto;
  }
  /* Hide the leading "N." numeral inside the cloned footnote body; the popover
     is already anchored to its ref, the number is redundant. */
  .fn-popover > strong:first-child { display: none; }
  .fn-popover a {
    color: var(--accent-2);
    text-decoration: underline;
    text-decoration-color: rgba(74, 107, 138, 0.4);
  }
  .fn-popover em { font-style: italic; color: var(--text); }
  .fn-popover .tweet-quote { font-style: italic; color: var(--text); }

  /* Research-paper preview reuses the shared popover on touch; neutralize the
     hover-card chrome so the pc-* typography renders inside the sheet, and turn
     the whole card into one tap target with a "Read paper" affordance. */
  .fn-popover.is-paper { cursor: pointer; }
  .fn-popover.is-paper .paper-card {
    display: block; position: static; width: auto; max-width: none;
    background: none; border: 0; border-radius: 0; padding: 0;
    box-shadow: none; z-index: auto; font-size: inherit; color: inherit;
    pointer-events: none;
  }
  .fn-popover .fn-open {
    display: block; margin-top: 12px; padding-top: 10px;
    border-top: 1px solid var(--line);
    font-family: var(--sans); font-size: 12.5px; font-weight: 600;
    color: var(--accent-2); letter-spacing: 0.01em;
  }

  /* Mobile: the popover is positioned AT the link (JS), not docked to the
     bottom. Link cards read as a quiet popover (ocean rail, small text action),
     footnotes as a note bubble (brass rail, numeral shown). 3px corners. */
  @media (max-width: 640px) {
    .fn-popover {
      border-radius: 3px;
      max-width: min(320px, calc(100vw - 24px));
      width: max-content;
      font-size: 13.5px;
      line-height: 1.55;
      padding: 13px 15px;
      max-height: 72vh;
      overflow-y: auto;
    }
    /* Note bubble: reveal the footnote numeral in brass. */
    .fn-popover.is-note > strong:first-child {
      display: inline; color: var(--accent); font-weight: 700; margin-right: 4px;
    }
    /* Quiet popover (link cards): ocean rail instead of the footnote brass. */
    .fn-popover.is-link { border-left-color: var(--accent-2); }
    /* Neutralize the reference-card chrome so its innards render inside the sheet. */
    .fn-popover.is-link .ref-card {
      display: block; position: static; width: auto; max-width: none;
      background: none; border: 0; border-radius: 0; padding: 0;
      box-shadow: none; z-index: auto; color: inherit; pointer-events: none;
    }
    .fn-popover.is-link .ref-card .hc-thumb-img { pointer-events: none; }
    /* Close affordance: tappable × in the top-right. */
    .fn-popover .fn-close {
      position: absolute;
      top: 5px;
      right: 7px;
      width: 26px;
      height: 26px;
      border: none;
      background: transparent;
      color: var(--muted);
      font-family: var(--sans);
      font-size: 17px;
      cursor: pointer;
      line-height: 1;
      padding: 0;
      align-items: center;
      justify-content: center;
    }
    .fn-popover .fn-close:active { color: var(--accent); }
    .fn-popover .fn-close { display: flex; }
    /* On phones the bottom footnotes list is redundant: every note opens as a
       tap popover (which clones from the #fn- nodes, so they stay in the DOM). */
    .footnotes { display: none; }
  }
  .fn-popover .fn-close { display: none; }

  /* Suppress the footnote popover on wide viewports where sidenotes already
     serve. Link cards (paper + reference) stay allowed at any width. */
  @media (min-width: 1200px) {
    .fn-popover:not(.is-paper):not(.is-link) { display: none !important; }
  }

  /* Footnote refs get a subtle brass underline so they're discoverable as
     interactive on touch. Desktop relies on hover affordance. */
  .essay sup a[href^="#fn-"] {
    text-decoration: none;
    color: var(--accent);
    font-weight: 600;
    padding: 0 1px;
    border-radius: 2px;
  }
  .essay sup a[href^="#fn-"]:hover {
    background: rgba(184, 134, 11, 0.14);
  }

  /* Touch devices: the CSS :hover card cannot fire, so keep it hidden and let
     JS show the shared popover on tap instead. Keep the ▸ cue visible so the
     phrase reads as tappable. */
  @media (hover: none) {
    a.paper .paper-card { display: none !important; }
  }
  /* Keyboard users: reveal the card on focus, same as hover. */
  a.paper:focus-visible .paper-card { display: block; }

  /* Acronym definition card. JS adds .tip-open on tap (touch); desktop opens
     it on :hover. The visual for BOTH states is ONE rule under #18 further
     down; do not add a copy here. Two equal-specificity copies is how the
     phone bug of 19-09-2026 happened: a real tap leaves the term sticky-:hover
     as well as .tip-open, both rules matched, and the later one overrode the
     clamped transform on source order. */
  /* On touch devices, suppress the desktop :hover variant (it fires briefly
     on tap) so the .tip-open class is the single source of truth. Exempt an
     already-open tooltip: a real tap makes the term sticky-:hover AND .tip-open,
     and without this exemption the display:none would also hide the tapped
     definition (the .tip-open rule sets display:block but ties on specificity
     and loses on source order). */
  @media (hover: none) {
    .acronym:not(.tip-open):hover::after { display: none; }
  }

  /* #10 SVG draw-on-enter ----------------------------------------------------- */
  @media (prefers-reduced-motion: no-preference) {
    .anim-svg rect[fill]:not([fill="none"]) {
      transform-origin: left center;
    }
    .anim-svg.animated path[stroke]:not([stroke="none"]):not(.v9-frontier):not(.v5-arrow):not(.v3-cone),
    .anim-svg.animated line[stroke]:not([stroke="none"]):not(.ladder-rail):not(.ladder-rung):not(.pns-bar):not(.v11-bracket-left):not(.v11-bracket-right):not(.v11-bracket-span),
    .anim-svg.animated polyline[stroke]:not([stroke="none"]) {
      animation: svgDrawIn 1.0s var(--ease-draw) forwards;
      will-change: stroke-dashoffset;
    }
    .anim-svg path[stroke]:not([stroke="none"]):not(.v9-frontier):not(.v5-arrow):not(.v3-cone),
    .anim-svg line[stroke]:not([stroke="none"]):not(.ladder-rail):not(.ladder-rung):not(.pns-bar):not(.v11-bracket-left):not(.v11-bracket-right):not(.v11-bracket-span),
    .anim-svg polyline[stroke]:not([stroke="none"]) {
      stroke-dasharray: 800;
      stroke-dashoffset: 800;
    }
    /* Ladder-label children (text + rect indicator) are animated as a group via
       .ladder-label fade-in; suppress the generic rect/text rules inside them. */
    .anim-svg-ladder .ladder-label rect,
    .anim-svg-ladder .ladder-label text {
      opacity: 1 !important;
      animation: none !important;
    }
    .anim-svg-ladder .ladder-anchor rect,
    .anim-svg-ladder .ladder-anchor text {
      opacity: 1 !important;
      animation: none !important;
    }
    /* Circles + polygons: visible by default so they always render even if
       IntersectionObserver fails to fire .animated (e.g., the user lands on
       the visual via deep link, or IO timing misses the threshold). Animation
       only enhances the entry; it does not gate visibility. */
    .anim-svg circle[fill]:not([fill="none"]),
    .anim-svg polygon[fill]:not([fill="none"]) {
      opacity: 1;
    }
    .anim-svg.animated circle[fill]:not([fill="none"]),
    .anim-svg.animated polygon[fill]:not([fill="none"]) {
      animation: svgFadeIn 0.75s var(--ease-draw);
      will-change: opacity;
    }
    @keyframes svgDrawIn {
      to { stroke-dashoffset: 0; }
    }
    @keyframes svgFadeIn {
      from { opacity: 0; }
      to { opacity: 1; }
    }

    /* Data charts (gridline-heavy) reveal as one unit: a smooth rise + fade,
       rather than drawing every gridline. Default opacity is 1 so the chart is
       never stuck hidden if the observer does not fire; .animated replays the
       entrance. Observed by the same IntersectionObserver (see JS). */
    .anim-svg-fade {
      opacity: 1;
      transform-origin: center bottom;
    }
    .anim-svg-fade.animated {
      animation: svgRiseIn 0.85s var(--ease-draw) both;
      will-change: transform, opacity;
    }
    @keyframes svgRiseIn {
      from { opacity: 0; transform: translateY(14px); }
      to   { opacity: 1; transform: translateY(0); }
    }

    /* Hero "Phase Lock Window" chart (.anim-plw): a choreographed line-draw reveal.
       The frame + gold window fade in, then the three capability curves DRAW
       themselves in sequence (human flat, machine sweep, BCI lift), labels settle
       last. Fires once. Hidden states are gated behind .plw-armed, which the JS
       adds only when it will actually animate, so reduced-motion / no-JS / no-IO
       readers always get the fully rendered static chart. Per-element timing
       (--d delay, --dur draw duration, --op target opacity) lives inline in the
       markup; faded elements transition to their own --op so faint gridlines are
       not blown out to full opacity. */
    .anim-plw.plw-armed .plw-fade {
      opacity: 0;
    }
    .anim-plw.plw-armed .plw-draw {
      stroke-dasharray: 1;
      stroke-dashoffset: 1;
    }
    .anim-plw.plw-armed.plw-in .plw-fade {
      animation: plwFade 0.6s var(--ease-draw) both;
      animation-delay: var(--d, 0s);
    }
    .anim-plw.plw-armed.plw-in .plw-draw {
      animation: plwDraw var(--dur, 0.7s) var(--ease-draw) both;
      animation-delay: var(--d, 0s);
      will-change: stroke-dashoffset;
    }
    /* Fades animate to each element's own --op so faint gridlines are not blown
       out to full opacity. A keyframe (not a transition) makes the reveal
       deterministic: the base opacity:0 holds until .plw-in plays this. */
    @keyframes plwFade { from { opacity: 0; } to { opacity: var(--op, 1); } }
    @keyframes plwDraw { to { stroke-dashoffset: 0; } }

    /* Generic sequenced reveal (.anim-seq): the .anim-plw pattern, generalized.
       Annotate elements with sq-draw (stroke draw; element needs pathLength="1"),
       sq-fade (fade to its own --op, default 1), or sq-pop (scale pop for dots).
       Per-element timing inline via --d (delay) / --dur (draw duration).
       Armed by JS only when it will actually animate, so no-JS / no-IO /
       reduced-motion readers always get the fully rendered static figure.
       Fires once (dedicated observer next to the .anim-plw one). The .01s
       seqVis flips a drawn stroke's opacity at its own start so marker-end
       arrowheads appear together with their arc instead of floating early. */
    .anim-seq.seq-armed .sq-draw { stroke-dasharray: 1; stroke-dashoffset: 1; opacity: 0; }
    .anim-seq.seq-armed .sq-fade { opacity: 0; }
    .anim-seq.seq-armed .sq-pop  { opacity: 0; transform: scale(.4); transform-origin: center; transform-box: fill-box; }
    .anim-seq.seq-armed.seq-in .sq-draw {
      animation: plwDraw var(--dur, .6s) var(--ease-draw) both, seqVis .01s linear both;
      animation-delay: var(--d, 0s);
      will-change: stroke-dashoffset;
    }
    .anim-seq.seq-armed.seq-in .sq-fade { animation: seqFade .5s var(--ease-draw) both; animation-delay: var(--d, 0s); }
    .anim-seq.seq-armed.seq-in .sq-pop  { animation: seqPop .5s cubic-bezier(0.34, 1.56, 0.64, 1) both; animation-delay: var(--d, 0s); }
    @keyframes seqVis  { to { opacity: var(--op, 1); } }
    @keyframes seqFade { from { opacity: 0; } to { opacity: var(--op, 1); } }
    @keyframes seqPop  { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: scale(1); } }
    /* sq-bar: a bar/column grows up from its own baseline (bar charts). */
    .anim-seq.seq-armed .sq-bar { transform: scaleY(0); transform-origin: center bottom; transform-box: fill-box; }
    .anim-seq.seq-armed.seq-in .sq-bar { animation: seqBar var(--dur, .7s) var(--ease-bar) both; animation-delay: var(--d, 0s); }
    @keyframes seqBar { from { transform: scaleY(0); } to { transform: scaleY(1); } }
    /* sq-wipe: sq-bar downward — a clip rect uncovers its target from its own top
       edge. For a stroke that cannot sq-draw because it already spends
       stroke-dasharray on its own dash pattern: the vis-1 projection rail is
       dashed, so it used to fade in whole while the solid rail above it drew,
       which read as a jump. Put this on a <rect> inside a <clipPath> and the
       dashes are uncovered at whatever speed --dur asks for, letting the
       projection continue the solid stroke instead of arriving after it. Unarmed
       (no JS, reduced motion) the rect sits at scale 1 and clips nothing. */
    .anim-seq.seq-armed .sq-wipe { transform: scaleY(0); transform-origin: center top; transform-box: fill-box; }
    .anim-seq.seq-armed.seq-in .sq-wipe { animation: seqWipe var(--dur, .7s) var(--ease-draw) both; animation-delay: var(--d, 0s); }
    @keyframes seqWipe { from { transform: scaleY(0); } to { transform: scaleY(1); } }

    /* Capital flywheel (fig §7): the builder JS sets pathLength="1", per-element
       animationDelay, and toggles -armed / -run — but the matching CSS was never
       written, so nothing animated. Classes start with a digit → attribute
       selectors. Hidden states gate on -armed (the JS skips arming under
       reduced-motion, so that path shows the static wheel). Inline animationDelay
       from the JS drives the stagger (same pattern as applyGraphStagger). Segs
       hide via dashoffset only, keeping their 0.9 opacity. Re-fires (spins again). */
    [class~="11-capital-flywheel-armed"] [class~="11-capital-flywheel-seg"] { stroke-dasharray: 1; stroke-dashoffset: 1; }
    [class~="11-capital-flywheel-armed"] [class~="11-capital-flywheel-arrowhead"],
    [class~="11-capital-flywheel-armed"] [class~="11-capital-flywheel-node"],
    [class~="11-capital-flywheel-armed"] [class~="11-capital-flywheel-center"] { opacity: 0; }
    [class~="11-capital-flywheel-armed"][class~="11-capital-flywheel-run"] [class~="11-capital-flywheel-seg"] {
      animation: plwDraw 0.5s var(--ease-draw) both;
    }
    [class~="11-capital-flywheel-armed"][class~="11-capital-flywheel-run"] [class~="11-capital-flywheel-arrowhead"] {
      animation: seqFade 0.3s var(--ease-draw) both;
    }
    [class~="11-capital-flywheel-armed"][class~="11-capital-flywheel-run"] [class~="11-capital-flywheel-node"] {
      animation: seqFade 0.5s var(--ease-draw) both;
    }
    [class~="11-capital-flywheel-armed"][class~="11-capital-flywheel-run"] [class~="11-capital-flywheel-center"] {
      animation: seqFade 0.5s var(--ease-draw) both;
      animation-delay: 0.9s;
    }

    /* Scale-fit trade-off (fig §12, #sf16-A): JS measures the frontier length
       into --sf16-len, sets per-node animation-delay, and toggles sf16-animate,
       but the CSS was never written. Arm-gated (JS adds sf16-armed only when it
       will animate). Frontier draws (with its arrowhead), nodes fade in over the
       portability→ order, the "Pareto frontier" label settles last. Re-fires. */
    #sf16-A.sf16-armed .sf16-frontier { stroke-dasharray: var(--sf16-len, 800); stroke-dashoffset: var(--sf16-len, 800); opacity: 0; }
    #sf16-A.sf16-armed .sf16-node,
    #sf16-A.sf16-armed .sf16-lift { opacity: 0; }
    /* The two zone squares set the stage: they fade + settle in first, before
       the frontier draws and the device nodes land on top. */
    #sf16-A.sf16-armed .sf16-zone { opacity: 0; transform: scale(.965); transform-origin: center; transform-box: fill-box; }
    #sf16-A.sf16-armed.sf16-animate .sf16-zone { animation: sf16ZoneIn .85s var(--ease-draw) both; }
    #sf16-A.sf16-armed.sf16-animate .sf16-zone-2 { animation-delay: .16s; }
    @keyframes sf16ZoneIn { from { opacity: 0; transform: scale(.965); } to { opacity: 1; transform: scale(1); } }
    #sf16-A.sf16-armed.sf16-animate .sf16-frontier {
      animation: plwDraw 1.4s var(--ease-draw) 0.2s both, seqVis 0.01s linear 0.2s both;
    }
    #sf16-A.sf16-armed.sf16-animate .sf16-node { animation: seqFade 0.6s var(--ease-draw) both; }
    #sf16-A.sf16-armed.sf16-animate .sf16-lift { animation: seqFade 0.6s var(--ease-draw) 1.3s both; }

    /* Graph-type SVGs: nodes pop in with stagger, edges connect after.
       The general .anim-svg circle fade is overridden here. */
    .anim-svg-graph circle[fill]:not([fill="none"]) {
      opacity: 0;
      transform: scale(0.3);
      transform-origin: center;
      transform-box: fill-box;
    }
    .anim-svg-graph.animated circle[fill]:not([fill="none"]) {
      animation: graphNodePop 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    }
    /* Edges (lines) wait until nodes have all landed, then draw */
    .anim-svg-graph line[stroke]:not([stroke="none"]) {
      stroke-dasharray: 220;
      stroke-dashoffset: 220;
    }
    .anim-svg-graph.animated line[stroke]:not([stroke="none"]) {
      animation: svgDrawIn 0.8s var(--ease-draw) forwards;
      animation-delay: var(--edge-delay, 1.1s);
    }
    /* Text labels and the ≈ symbol fade in early for context */
    .anim-svg-graph text {
      opacity: 0;
    }
    .anim-svg-graph.animated text {
      animation: svgFadeIn 0.4s var(--ease-draw) forwards;
      animation-delay: 0.1s;
    }
    @keyframes graphNodePop {
      0%   { opacity: 0; transform: scale(0.3); }
      55%  { opacity: 1; transform: scale(1.15); }
      100% { opacity: 1; transform: scale(1); }
    }
    .anim-svg-ladder .ladder-rung {
      opacity: 0;
      stroke-dasharray: 60;
      stroke-dashoffset: 60;
    }
    .anim-svg-ladder.animated .ladder-rung {
      animation: ladderRungDraw 0.4s var(--ease-draw) forwards;
    }
    .anim-svg-ladder .ladder-label {
      opacity: 0;
      transform: translateX(-6px);
    }
    .anim-svg-ladder.animated .ladder-label {
      animation: ladderLabelFade 0.5s var(--ease-draw) forwards;
    }
    .anim-svg-ladder .ladder-anchor {
      opacity: 0;
      transform-origin: center;
    }
    .anim-svg-ladder.animated .ladder-anchor {
      animation: ladderAnchorPulse 0.9s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    }
    @keyframes ladderRungDraw {
      to { opacity: 1; stroke-dashoffset: 0; }
    }
    @keyframes ladderLabelFade {
      to { opacity: 1; transform: translateX(0); }
    }
    @keyframes ladderAnchorPulse {
      0%   { opacity: 0; transform: scale(0.6); }
      60%  { opacity: 1; transform: scale(1.12); }
      100% { opacity: 1; transform: scale(1); }
    }

    /* Modality trajectory: words and arrows appear left to right, NEURAL lands with a spring */
    .anim-svg-trajectory .traj-word,
    .anim-svg-trajectory .traj-arrow,
    .anim-svg-trajectory .traj-neural {
      opacity: 0;
    }
    .anim-svg-trajectory.animated .traj-word,
    .anim-svg-trajectory.animated .traj-arrow {
      animation: svgFadeIn 0.55s var(--ease-draw) forwards;
    }
    .anim-svg-trajectory.animated .traj-word:nth-of-type(1) { animation-delay: 0.10s; }
    .anim-svg-trajectory.animated .traj-arrow:nth-of-type(1) { animation-delay: 0.28s; }
    .anim-svg-trajectory.animated .traj-word:nth-of-type(2) { animation-delay: 0.46s; }
    .anim-svg-trajectory.animated .traj-arrow:nth-of-type(2) { animation-delay: 0.64s; }
    .anim-svg-trajectory.animated .traj-word:nth-of-type(3) { animation-delay: 0.84s; }
    .anim-svg-trajectory.animated .traj-arrow:nth-of-type(3) { animation-delay: 1.02s; }
    .anim-svg-trajectory.animated .traj-word:nth-of-type(4) { animation-delay: 1.22s; }
    .anim-svg-trajectory.animated .traj-arrow:nth-of-type(4) { animation-delay: 1.40s; }
    .anim-svg-trajectory.animated .traj-word:nth-of-type(5) { animation-delay: 1.60s; }
    .anim-svg-trajectory.animated .traj-arrow.traj-arrow-final { animation-delay: 1.78s; }
    /* NEURAL lands LAST with an overshoot: the punchline. Two traps, both hit before:
       1) The overshoot is authored in the keyframes (the 60% bump), so the easing must NOT
          also overshoot — CSS applies the timing function to every keyframe segment, and a
          spring curve on top of a spring bump double-bounces. Expo-out keeps one clean pop.
       2) The scale must not rely on transform-box:fill-box — iOS Safari ignores fill-box on
          SVG <text> and falls back to the view-box, so the origin lands mid-canvas, ~316
          user-units left of the word, and the pop plays as a sideways boomerang on phones.
          The word's own center (686,31, matching the <text> x/y) is baked into the keyframes
          as a translate·scale·translate sandwich; origin 0 0 is the view-box corner in every
          engine, i.e. no hidden offset. */
    .anim-svg-trajectory.animated .traj-neural {
      animation: trajNeuralLand 0.65s var(--ease-draw) 1.96s forwards;
      transform-origin: 0 0;
    }
    @keyframes trajNeuralLand {
      0%   { opacity: 0; transform: translate(686px, 31px) scale(0.85) translate(-686px, -31px); }
      60%  { opacity: 1; transform: translate(686px, 31px) scale(1.12) translate(-686px, -31px); }
      100% { opacity: 1; transform: translate(686px, 31px) scale(1) translate(-686px, -31px); }
    }
  }

  /* #18 Acronym tooltips (hover + tap) ---------------------------------------- */
  /* ONE rule for both open states: .tip-open (set by the tap handler on touch)
     and :hover (desktop). Cream paper-card family, shared with .fn-popover and
     a.paper .paper-card so the on-demand overlays read as one system.

     It used to be two identical blocks (this one and a copy near the top of the
     stylesheet) at equal specificity. On a real iPhone a tap leaves the term
     sticky-:hover AS WELL AS .tip-open, so both matched and this later block
     won `transform` on source order: the --tip-shift-x the JS had just written
     was discarded and the card ran off the screen edge (19-09-2026). A single
     selector list cannot tie with itself. The JS clamp now also runs on hover,
     so the var is honoured on every path; it stays 0 whenever the card fits. */
  .acronym {
    border-bottom: 1px dotted var(--accent);
    position: relative;
    cursor: help;
  }
  .acronym.tip-open::after,
  .acronym:hover::after {
    content: attr(data-def);
    display: block;
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(calc(-50% + var(--tip-shift-x, 0px)));
    background: var(--bg-2);
    border: 1px solid var(--line);
    color: var(--text-2);
    font-family: var(--sans);
    font-style: normal;
    font-weight: 400;
    font-size: 12px;
    line-height: 1.5;
    padding: 12px 14px;
    border-radius: 6px;
    white-space: normal;
    width: 240px;
    max-width: 80vw;
    z-index: 50;
    box-shadow: 0 8px 28px rgba(44, 36, 22, 0.15);
    pointer-events: none;
  }
  /* Flip the card below the term when it would clip the top of the viewport.
     JS toggles .tip-below. No arrow to flip; the paper-card family does not
     use arrows. */
  .acronym.tip-open.tip-below::after,
  .acronym.tip-below:hover::after {
    bottom: auto;
    top: calc(100% + 8px);
  }
  /* Mobile-only 3px corners, matching the link/footnote overlays. */
  @media (max-width: 640px) {
    .acronym.tip-open::after,
    .acronym:hover::after { border-radius: 3px; }
  }

  /* #19 Arxiv hover previews on paper links ----------------------------------- */
  /* Paper links get a small brass marker AFTER the link text so the reader
     knows a preview card is available on hover. */
  a.paper {
    position: relative;
  }
  /* <em> nested inside a link (e.g. book / paper titles) should inherit the
     link color; the `p em` color rule at the top of the stylesheet would
     otherwise repaint italic link text in body-text brown. */
  a em, a.paper em { color: inherit; }
  /* The reference marker (.lk-mk), shared by paper links and cross-reference
     links so the cue reads as one system.

     It is a real <span> in the markup, sitting immediately AFTER </a>, and that
     placement is the whole point. As a `::after` inside the link it was an
     inline-block, i.e. an atomic inline, and the line breaker is free to break
     before one of those whatever precedes it, so the triangle kept wrapping onto
     a line of its own (14 of 321 column widths, measured). Two touching runs of
     ordinary text have no break opportunity between them, so as a sibling span
     it cannot be separated (0 of 321). Note a no-break space in front of the old
     inline-block did NOT fix it: nothing glues an atomic inline.

     Outside the <a> for a second reason: text inside a link takes the link's
     underline and cannot refuse it, so an inside-the-link fix drags the
     underline under the triangle. Out here the underline still stops at the last
     letter, which is the look we want.

     Adding a new marked link? Add the span too; there is no CSS that can
     generate content outside an element. Same reason numbered labels carry
     `Part&nbsp;III`, `Section&nbsp;17`, `Appendix&nbsp;D`: a reference and its
     number are one unit. */
  .lk-mk {
    margin-left: 4px;
    color: var(--accent);
    font-size: 0.78em;
    vertical-align: 1px;
    font-weight: 600;
    pointer-events: none;
    user-select: none;
  }
  /* The marker sits outside the anchor, so without this the hover target would
     stop at the last letter and drifting onto the triangle would close the
     preview card. The negative margin cancels the padding, so advance width and
     line breaking are unchanged; inline padding is not underlined, so the
     underline still ends at the text. */
  a.paper, a.reflink { padding-right: 11px; margin-right: -11px; }
  a.paper .paper-card {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    width: 340px;
    max-width: 80vw;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 14px 16px;
    font-family: var(--sans);
    font-style: normal;
    font-weight: 400;
    font-size: 12px;
    line-height: 1.5;
    color: var(--text-2);
    z-index: 35;
    box-shadow: 0 8px 28px rgba(44, 36, 22, 0.15);
    text-decoration: none;
    pointer-events: none;
    cursor: default;
  }
  a.paper:hover .paper-card { display: block; }
  .paper-card .pc-title {
    display: block;
    font-family: var(--serif-display);
    font-style: italic;
    font-size: 14.5px;
    font-weight: 500;
    color: var(--text);
    margin-bottom: 4px;
    line-height: 1.35;
  }
  .paper-card .pc-authors {
    display: block;
    font-size: 11px;
    color: var(--muted);
    margin-bottom: 8px;
    letter-spacing: 0.02em;
  }
  .paper-card .pc-abstract {
    display: block;
    font-size: 12px;
    color: var(--text-2);
  }
  /* Reference cards (section / part / diagram cross-refs): same on-demand overlay
     chrome as a.paper .paper-card, different innards. Desktop reveal on hover. */
  a.reflink { position: relative; }
  a.reflink .ref-card {
    display: none; position: absolute; top: calc(100% + 8px); left: 0; width: 300px; max-width: 80vw;
    background: var(--bg-2); border: 1px solid var(--line); border-radius: 6px; padding: 13px 15px;
    font-family: var(--sans); font-style: normal; font-weight: 400; font-size: 12px; line-height: 1.5;
    color: var(--text-2); z-index: 35; box-shadow: 0 8px 28px rgba(44,36,22,0.15);
    text-decoration: none; pointer-events: none; cursor: default; text-align: left; white-space: normal;
  }
  a.reflink.card-flip .ref-card { left: auto; right: 0; }
  a.reflink:hover .ref-card, a.reflink:focus-visible .ref-card { display: block; }
  @media (hover: none) { a.reflink .ref-card { display: none !important; } }
  .ref-card .hc-secrow { display: flex; gap: 11px; align-items: center; }
  .ref-card .hc-cart { width: 60px; height: 60px; flex: none; border-radius: 5px; object-fit: cover; opacity: .92; background: rgba(44,36,22,.05); }
  .ref-card .hc-secmain { display: block; min-width: 0; }
  .ref-card .hc-eyebrow { display: block; font-family: var(--mono); font-size: 10.5px; color: var(--accent-strong); font-weight: 600; letter-spacing: .03em; margin-bottom: 3px; }
  .ref-card .hc-title { display: block; font-family: var(--serif-display); font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 3px; line-height: 1.25; }
  .ref-card .hc-q { display: block; font-family: var(--serif); font-size: 12px; font-style: italic; color: var(--muted); line-height: 1.4; }
  .ref-card .q-part { font-family: var(--mono); font-size: 9.5px; font-weight: 600; font-style: normal; text-transform: uppercase; letter-spacing: .04em; color: var(--accent-strong); }
  .ref-card .hc-dlabel { display: block; font-family: var(--serif-display); font-size: 14px; font-weight: 600; color: var(--text); margin-bottom: 7px; }
  .ref-card .hc-thumb-img { display: block; width: 100%; height: auto; max-height: 200px; object-fit: contain; border: 1px solid var(--line); border-radius: 5px; background: var(--bg); }

  /* #21 Prediction cards ------------------------------------------------------ */
  .predictions {
    display: grid;
    gap: 14px;
    margin: 22px 0;
  }

  .the-bet {
    background: color-mix(in srgb, var(--accent) 4%, transparent);
    border-left: 3px solid var(--accent);
    padding: 18px 24px;
    margin: 2em 0;
    border-radius: 4px;
  }
  .the-bet li strong, .the-bet p strong { color: var(--text); font-weight: 700; }
  .why-now-list li strong { color: var(--text); font-weight: 700; }
  ul li strong { color: var(--text); font-weight: 700; }
  .the-bet p { font-size: 16.5px !important; line-height: 1.7 !important; }
  .the-bet li { font-size: 16.5px !important; line-height: 1.65 !important; }
  .the-bet ol, .the-bet ul { font-size: 16.5px !important; }

  /* Objection cards (S23) ----------------------------------------------------- */
  /* §24 objections: a deep-brass serif "NN." numeral (h3 tint) runs straight into
     the bold claim, print-numbered-list style; then setup + RESPONSE run-in.
     Chosen via case-against-styles-playground.html ("Numbered Claim"). */
  .obj-index {
    font-weight: 700;
    color: var(--accent-strong);
    margin-right: 0.45em;
    font-variant-numeric: lining-nums;
  }
  .obj-claim { margin: 2em 0 0.5em; }
  .obj-setup { margin: 0 0 0.8em; }
  .obj-resp-label {
    font-family: var(--sans);
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
    margin-right: 8px;
  }

  /* #24 Citation tiers in references ------------------------------------------ */
  /* Appendix disclosure panels (Sat's pick from the appendix-headings
     playground, 20-08-2026): the visual-frame-adjacent look, chosen with the
     figure-idiom overlap flagged. Web+mobile only: the PDF deliberately takes
     the editorial h3 treatment instead (pdf/make-spike-excerpt.py,
     .pdf-details/.pdf-summary), because these blocks are taller than a page and
     a frame would have to split across breaks. The open-state gap comes from
     the existing 12px inner wrapper div, so the summary itself needs no [open]
     margin. */
  .apx-panel {
    margin: 1.2em 0;
    border: 1px solid rgba(44, 36, 22, 0.16);
    background: rgba(184, 134, 11, 0.045);
    border-radius: 5px;
    padding: 14px 18px;
  }
  .apx-panel,
  .apx-panel > summary {
    /* mobile: kill the grey tap-highlight flash on toggle (keyboard focus
       outline untouched) */
    -webkit-tap-highlight-color: transparent;
  }
  .apx-panel > summary {
    cursor: pointer;
    font-family: var(--sans);
    font-weight: 600;
    font-size: 11.5px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--accent-strong);
    padding: 2px 0;
  }
  @media (max-width: 640px) {
    .apx-panel { padding: 12px 14px; }
  }
  /* Appendix D's group headings live inside its panel: compact (20px -> 16px)
     so they subordinate to the smallcaps summary. !important because the
     <=760px readability pass sets .essay h3 to 18.5px !important. */
  .essay .apx-panel h3 { font-size: 16px !important; margin: 20px 0 8px; }
  .ref-tier {
    margin-bottom: 24px;
  }
  .ref-tier .rt-label {
    font-family: var(--sans);
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--muted);   /* stepped down from brass so the panel title above it wins (20-08-2026) */
    font-weight: 600;
    margin-bottom: 8px;
  }
  .ref-tier ul {
    margin: 0;
    padding-left: 22px;
  }
  .ref-tier ul li {
    margin-bottom: 5px;
    font-size: 15px;
    line-height: 1.55;
  }

  /* #25 Version history footer ------------------------------------------------ */
  .version-history {
    margin: 40px 0 0;
    padding: 22px 26px;
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 6px;
    font-family: var(--sans);
  }
  .version-history .vh-title {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--muted);
    font-weight: 600;
    margin-bottom: 14px;
  }
  .version-history .vh-entry {
    font-size: 13px;
    color: var(--text-2);
    line-height: 1.55;
    padding: 7px 0;
    border-bottom: 1px solid var(--line);
  }
  .version-history .vh-entry:last-child { border-bottom: none; }
  .version-history .vh-date {
    font-family: var(--mono);
    color: var(--accent);
    font-weight: 600;
    margin-right: 10px;
  }

  /* N2 Last-updated indicator ------------------------------------------------- */
  .last-updated {
    display: inline-block;
    font-family: var(--sans);
    font-size: 12px;
    color: var(--muted);
    margin: 0;
    letter-spacing: 0.02em;
  }
  .last-updated::before { content: "\00B7"; margin: 0 8px; color: var(--dim); }
  .last-updated .lu-tag { margin-right: 4px; }
  .last-updated a {
    color: var(--muted);
    text-decoration: underline;
    text-decoration-color: rgba(123, 107, 88, 0.4);
  }

  /* #27b Opening definition entry (white card, below masthead) ---------------- */
  .essay-header { margin-bottom: 26px; } /* was 40px; tightened for the definition card */
  #exec-summary { margin-top: 26px; }    /* h2 default is 50px; the card carries its own rhythm */
  .opening-def {
    background: linear-gradient(rgba(184, 134, 11, 0.03), rgba(184, 134, 11, 0.03)), #FFFFFF;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 16px 19px;
    box-shadow: 0 3px 8px rgba(44, 36, 22, 0.06);
    margin: 0 0 6px;
  }
  .opening-def .od-head { margin: 0 0 9px; padding-bottom: 8px; border-bottom: 1px solid var(--line); }
  .opening-def .od-head .od-hw { font-family: var(--serif-display); font-style: italic; font-size: 19px; color: var(--text); }
  .opening-def .od-head .od-ipa { font-family: var(--mono); font-size: 10px; color: var(--muted); margin-left: 0.5em; }
  .opening-def .od-head .od-pos { font-family: var(--serif-display); font-style: italic; font-size: 15px; color: var(--muted); margin-left: 0.35em; }
  .opening-def .od-lead { font-family: var(--serif); font-size: 15px; line-height: 1.6; color: var(--text-2); margin: 0 0 6px; }
  .opening-def .od-dom { font-family: var(--serif); font-size: 15px; line-height: 1.6; color: var(--text-2); margin: 0 0 6px; }
  .opening-def .od-lin { color: var(--accent-strong); white-space: nowrap; }
  .opening-def p:last-child { margin-bottom: 0; }
  .opening-def .od-turn { font-family: var(--serif); font-size: 15px; line-height: 1.6; color: var(--text-2); margin: 12px 0 0; }
  .pl-term { font-family: var(--serif-display); font-style: italic; color: var(--accent-strong); font-size: 16px; /* optical match: EB Garamond runs small */ }
  @media (max-width: 760px) {
    /* the mobile h2 rule uses !important margins; match it so the card keeps its rhythm */
    #exec-summary { margin-top: 30px !important; }
  }
  .last-updated a:hover { color: var(--accent); }

  /* Tweet treatment: stripped back to SA / AI 2027 register.
     The prose introduces the source ("From a May 2024 tweet:"), the link is a
     normal link, the quote is italic because it is a quote. No glyphs, no
     handle labels, no brass chrome. Citations are citations, regardless of
     platform; the argument is what matters, not the platform metadata.
     Classes .tweet-link / .tweet-anchor are left in the HTML as no-ops in
     case future styling decisions want them back. */
  .tweet-quote {
    font-style: italic;
  }

  /* Toast for clipboard-copy feedback ----------------------------------------- */
  .toast {
    position: fixed;
    top: 28px;
    left: 50%;
    transform: translate(-50%, -10px);
    background: var(--accent);
    color: #0e0e0e;
    font-family: var(--sans);
    font-size: 12.5px;
    font-weight: 600;
    padding: 9px 18px;
    border-radius: 6px;
    box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25);
    z-index: 200;
    opacity: 0;
    transition: opacity 0.2s ease, transform 0.2s ease;
    pointer-events: none;
  }
  .toast.show {
    opacity: 1;
    transform: translate(-50%, 0);
  }
  /* ============================================================================
     Chapter plates + inline image figures (v3 final ship)
     Six chapter plates between movement ranges; three inline image figures.
     Voyage Bridge palette holds.
     ============================================================================ */
  .chapter-plate {
    margin: 64px calc(-1 * var(--page-pad, 24px)) 44px;
    padding: 26px 0 28px;
    border-top: 1px solid rgba(184, 134, 11, 0.45);
    border-bottom: 1px solid rgba(184, 134, 11, 0.45);
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .chapter-plate .plate-num {
    font-family: var(--sans);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.28em;
    color: var(--accent);
    font-weight: 700;
    margin-bottom: 8px;
  }
  .chapter-plate .plate-title {
    font-family: var(--serif-display);
    font-size: 38px;
    font-style: italic;
    color: var(--text);
    line-height: 1;
    margin: 0 0 6px;
    font-weight: 500;
    letter-spacing: -0.005em;
  }
  .chapter-plate .plate-sub {
    font-family: var(--serif);
    font-size: 14px;
    font-style: italic;
    color: var(--muted);
    max-width: 420px;
    margin: 0 auto;
    line-height: 1.5;
  }
  .chapter-plate .plate-img {
    max-width: 140px;
    width: 100%;
    height: auto;
    margin: 0 auto 22px;
    display: block;
    border-radius: 3px;
    opacity: 0.92;
    order: -1;
  }

  /* inline image figure (uses .visual-frame structure, just with an <img>) */
  .visual-frame .essay-img {
    width: 100%;
    height: auto;
    display: block;
    border-radius: 4px;
  }
  .field-notes-marker .fn-label {
    font-family: var(--sans);
    text-transform: uppercase;
    letter-spacing: 0.26em;
    font-size: 10.5px;
    color: var(--accent);
    font-weight: 700;
    margin-bottom: 4px;
  }
  .field-notes-marker .fn-title {
    font-family: var(--serif-display);
    font-style: italic;
    font-size: 22px;
    color: var(--muted);
  }

/* ===== Predictions appendix (scoped to #p2r) ===== */
#p2r{--hit:#5f7d4f;--partial:var(--accent);--open:#8a8073;--missed:var(--red)}
#p2r .p2r-lead{font-size:17px;margin:14px 0 16px}
/* figure frame — same visual language as the essay's .visual-frame figures */
#p2r .vf{background:var(--bg-2);border:1px solid var(--line);border-radius:8px;padding:20px 18px 12px;margin:14px 0 4px}
#p2r .vf-head{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap}
#p2r .vis-label{font-family:var(--sans);font-size:10px;text-transform:uppercase;letter-spacing:.14em;color:var(--accent);font-weight:600}
#p2r .pillseg{display:inline-flex;background:rgba(44,36,22,.055);border-radius:999px;padding:3px}
#p2r .pillseg button{font-family:var(--sans);font-size:11.5px;font-weight:600;border:0;background:transparent;color:var(--muted);padding:4px 13px;border-radius:999px;cursor:pointer;transition:all .12s}
#p2r .pillseg button.on{background:#fff;color:var(--text);box-shadow:0 1px 3px rgba(44,36,22,.14)}
#p2r .chartbox.hide{display:none}
#p2r .cap{font-family:var(--sans);font-size:12.5px;color:var(--muted);text-align:center;margin:6px 8px 2px}
#p2r .chartbox svg{display:block;width:100%;height:auto;font-family:var(--sans)}
#p2r .node{cursor:pointer;transition:transform .12s ease;transform-box:fill-box;transform-origin:center}
#p2r .lane{stroke:var(--line);stroke-width:1;vector-effect:non-scaling-stroke;shape-rendering:crispedges}
#p2r .axis{stroke:rgba(44,36,22,.14);vector-effect:non-scaling-stroke;shape-rendering:crispedges}
#p2r .axis-txt{fill:var(--muted);font-size:11.5px;font-family:var(--mono);opacity:.85}
#p2r .ax-title{fill:var(--muted);font-size:9.5px;font-weight:600;letter-spacing:.14em;font-family:var(--sans);opacity:.8}
#p2r .lane-txt{fill:var(--text);font-size:12px;font-weight:600;font-family:var(--sans)}
#p2r .node.hot{transform:scale(1.55)}
#p2r .node.dim{opacity:.28}
/* Entrance runs on wrapper groups (.p2r-el frame/lanes, .p2r-dot per dot), never on
   .node itself, so the hover hot/dim cascade keeps working after the classes strip. */
@media (prefers-reduced-motion: no-preference){
  #p2r svg.p2r-armed .p2r-el,#p2r svg.p2r-armed .p2r-dot{opacity:0}
  #p2r svg.p2r-armed .p2r-dot{transform:scale(.3);transform-box:fill-box;transform-origin:center}
  #p2r svg.p2r-armed.p2r-in .p2r-el{opacity:1;transition:opacity .55s ease var(--d,0s)}
  #p2r svg.p2r-armed.p2r-in .p2r-dot{opacity:1;transform:scale(1);transition:opacity .45s ease var(--d,0s),transform .45s cubic-bezier(0.34,1.56,0.64,1) var(--d,0s)}
}
/* PDF: the view toggle is meaningless on paper, and the observer never fires there,
   so force every entrance element visible. */
@media print{
  #p2r .pillseg{display:none}
  #p2r svg .p2r-el,#p2r svg .p2r-dot{opacity:1!important;transform:none!important}
}
#p2r .grp{font-family:var(--serif-display);font-size:20px;font-weight:600;letter-spacing:-.005em;margin:32px 0 12px;color:var(--accent-strong)}
/* year-spine prediction rows */
#p2r .sp-group{margin:0 0 4px}
/* The wash is the row's only highlight, so it needs air on both sides: with padding:0 the
   confidence figure sat exactly on its right edge and the band read as clipped. Padding +
   equal negative margin bleeds the paint outward and leaves every child pixel-identical. */
#p2r .bet{display:grid;grid-template-columns:22px minmax(0,1fr) auto;align-items:center;padding:8px 12px;margin:0 -12px;position:relative;border-radius:6px;transition:background .12s}
#p2r .rmeta{display:flex;align-items:center;gap:6px;justify-content:flex-end;white-space:nowrap;margin-left:12px}
#p2r .yhead{font-family:var(--mono);font-size:13px;font-weight:700;color:var(--text);letter-spacing:.02em;margin:26px 0 8px;display:flex;align-items:center;gap:10px}
#p2r .yhead:first-child{margin-top:26px}
#p2r .yhead::after{content:"";flex:1;height:1px;background:var(--line)}
#p2r .ptag{display:inline-flex;align-items:center;font-family:var(--sans);font-size:10.5px;font-weight:700;letter-spacing:.02em;white-space:nowrap;padding:2px 8px;border-radius:999px;background:transparent;border:1.4px solid;vertical-align:middle;margin:0}
#p2r .bet.hot{background:rgba(74,107,138,.08)}
#p2r .bet.flash{animation:p2rflash 1.6s ease}
@keyframes p2rflash{0%,35%{background:rgba(184,134,11,.18)}100%{background:transparent}}
#p2r .sp-node{position:relative;display:flex;align-items:center;justify-content:center;align-self:stretch}
#p2r .sp-node::before{content:"";position:absolute;top:-8px;bottom:-8px;left:50%;width:1px;background:rgba(44,36,22,.18)}
#p2r .sp-group .bet:first-child .sp-node::before{top:50%}
#p2r .sp-group .bet:last-child .sp-node::before{bottom:50%}
#p2r .sp-node svg{position:relative;z-index:1;width:13px;height:13px;background:var(--bg);border-radius:50%}
#p2r .sp-claim{font-family:var(--sans);font-size:14.5px;font-weight:500;color:var(--text-2);line-height:1.45;padding-left:10px}
#p2r .sp-conf{font-family:var(--mono);font-size:11.5px;color:var(--muted);white-space:nowrap}
#p2rtip{position:fixed;z-index:60;pointer-events:none;display:none;opacity:0;transition:opacity .1s;max-width:280px;background:var(--text);color:#f4ede1;border-radius:6px;padding:10px 12px;font-family:var(--sans);font-size:12.5px;box-shadow:0 6px 22px rgba(0,0,0,.22)}
#p2rtip .tt-claim{font-family:var(--serif);font-size:13.5px;line-height:1.4;margin-bottom:6px;color:#fff}
#p2rtip .tt-row{display:flex;gap:8px;align-items:center;color:#d8cdbb;font-size:11.5px;margin-top:3px}
#p2rtip .tt-row .g{width:9px;height:9px;border-radius:50%}
@media (max-width:640px){
  #p2r .vf{padding:14px 10px 8px}
  #p2r .vf-head{gap:8px}
  #p2r .chartbox{overflow-x:auto;-webkit-overflow-scrolling:touch}
  #p2r .bet{grid-template-columns:20px minmax(0,1fr) auto;padding:8px 10px;margin:0 -10px}
  #p2r .sp-claim{font-size:13.5px}
}
/* ===== Appendix D interactive modality matrix (#dmatrix) ===== */
#dmatrix{margin:2px 0 0}
#dmatrix .dm-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
#dmatrix table{width:100%;border-collapse:collapse;font-family:var(--sans)}
#dmatrix th,#dmatrix td{padding:6px 7px;border-bottom:1px solid var(--line);text-align:left;vertical-align:middle}
#dmatrix th{font-size:10px;letter-spacing:.02em;text-transform:uppercase;color:var(--muted);cursor:pointer;white-space:nowrap;user-select:none}
#dmatrix th:first-child{cursor:default}
#dmatrix th.sorted{color:var(--accent)}
#dmatrix th.num,#dmatrix td.num{text-align:center}
#dmatrix td.dm-name{font-family:var(--serif);font-size:13.5px;font-weight:600;white-space:nowrap;color:var(--text)}
#dmatrix tbody tr{transition:background .1s}
/* Row highlight. Touch has no hover and iOS leaves a tap stuck in it, so .dm-hot is the
   only source of truth there (same reasoning as the .acronym:not(.tip-open):hover rule).
   Gating this is not cosmetic: iOS applies :hover on the first tap and, if that paints
   anything, WITHHOLDS the click so the reader can see the hover state. One ungated rule
   here cost the first tap on every row and every column header. Confirmed 18-08-2026.
   The .dm-name pair keeps the pinned column opaque under a highlight: --accent-soft is
   translucent and outranks the sticky rule below, so without it the row's own score bars
   slid visibly under its name as you swiped. Compositing the wash over an opaque base
   beats hardcoding the blend, and needs no width guard: above 720px the cell has no
   background of its own and sits on the same var(--bg-2) frame, so it renders identically. */
@media (hover:hover){
  #dmatrix tbody tr:hover td{background:var(--accent-soft)}
  #dmatrix tbody tr:hover td.dm-name{
    background-color:var(--bg-2);
    background-image:linear-gradient(var(--accent-soft),var(--accent-soft))}
}
#dmatrix tbody tr.dm-hot td{background:var(--accent-soft)}
#dmatrix tbody tr.dm-hot td.dm-name{
  background-color:var(--bg-2);
  background-image:linear-gradient(var(--accent-soft),var(--accent-soft))}
#dmatrix .dm-track{display:inline-flex;align-items:center;width:39px;height:9px;border-radius:5px;background:rgba(44,36,22,.08);vertical-align:middle;overflow:hidden}
#dmatrix .dm-bar{display:inline-block;height:9px;border-radius:5px;vertical-align:middle}
#dmatrix .dm-dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px;vertical-align:middle}
#dmatrix th .dm-caret{margin-left:3px;font-size:9px;opacity:.3;transition:opacity .15s}
@media (hover:hover){#dmatrix th.num:hover{color:var(--accent)}#dmatrix th.num:hover .dm-caret{opacity:.7}}
#dmatrix th.sorted .dm-caret{opacity:1}
/* watermark: this figure is an HTML table, not an SVG, so it cannot carry the
   in-viewBox text the chart figures use. Sits as a static right-aligned line
   under the last row's rule, outside .dm-scroll so horizontal scrolling on
   narrow screens never carries it out of view. */
#dmatrix .dm-wm{margin-top:7px;text-align:right;font-family:var(--mono);font-size:8.5px;color:var(--muted);opacity:.45;pointer-events:none;user-select:none}
/* Phones cannot fit an 8-column matrix (645px of table in a 331px window), so it scrolls.
   Pin the modality name so you always know which row you are reading, and fade the right
   edge so the cut reads as "there is more" rather than as broken.
   The fade sits in .dm-wrap, NOT inside .dm-scroll: an absolutely positioned child of a
   scroll container anchors to the SCROLLED content box, so a right:0 fade placed inside
   travels leftward as you swipe and ends up painting over the pinned name column, eating a
   character out of the middle of it. Verified 17-08-2026. */
#dmatrix .dm-wrap{position:relative}
#dmatrix .dm-foot{display:flex;align-items:baseline;gap:12px}
#dmatrix .dm-foot .dm-wm{margin-left:auto}
#dmatrix .dm-fade,#dmatrix .dm-swipe{display:none}
@media (max-width:720px){
  /* sticky <td> under border-collapse:collapse misbehaves in Safari; separate borders with
     the row rules moved onto the cells is the standard workaround and looks identical */
  #dmatrix table{border-collapse:separate;border-spacing:0}
  #dmatrix th,#dmatrix td{border-bottom:1px solid var(--line)}
  #dmatrix td.dm-name,#dmatrix thead th:first-child{position:sticky;left:0;z-index:2;
    background:var(--bg-2);box-shadow:1px 0 0 var(--line)}
  /* the rule that keeps this pinned cell opaque under a row highlight lives with the
     other highlight rules above, not here: it has to be gated on hover, and it needs no
     width guard to render correctly. */
  #dmatrix.is-scrollable .dm-fade{display:block;position:absolute;top:0;right:0;width:14px;height:100%;
    pointer-events:none;z-index:3;background:linear-gradient(90deg,rgba(255,255,255,0),var(--bg-2) 85%)}
  #dmatrix.is-scrollable .dm-swipe{display:block;margin-top:7px;font-family:var(--sans);font-size:9.5px;
    letter-spacing:.06em;color:var(--muted);opacity:.75}
}
/* Print has no scrolling, and the print CSS already forces this table to full width at 8.5px.
   Reset here too so the sticky column can never leak into the PDF at whatever width it renders. */
@media print{
  #dmatrix table{border-collapse:collapse !important}
  #dmatrix td.dm-name,#dmatrix thead th:first-child{position:static !important;box-shadow:none !important}
  #dmatrix .dm-fade,#dmatrix .dm-swipe{display:none !important}
  #dmatrix tbody tr.dm-hot td{background:none !important}
}
.visual-frame .dm-labelrow{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px}
.visual-frame .dm-labelrow .vis-label{margin-bottom:0}
.visual-frame .dm-hint{font-family:var(--sans);font-size:10px;letter-spacing:.03em;color:var(--muted);white-space:nowrap;opacity:.85}
/* The cue has to name the gesture the reader actually has. Word-swap idiom of the
   .vrl-w-hover/.vrl-w-tap hints. The short touch phrasing is deliberate: the label
   row is one nowrap line beside a wrappable title, and "rank by any column" pushes
   "The modality map" onto a second line at 360px. */
.visual-frame .dm-hint .dm-w-tap{display:none}
@media (hover:none){
  .visual-frame .dm-hint .dm-w-hover{display:none}
  .visual-frame .dm-hint .dm-w-tap{display:inline}
}
/* Modality-map entrance: rows fade + slide up and their score-bars grow from the
   left, staggered top-to-bottom on scroll-in; replays on re-entry. Sorting
   re-renders instantly (new rows are born in the final .dm-in state). Reduced
   motion: the JS never adds .dm-armed, so the table renders complete. */
@media (prefers-reduced-motion: no-preference){
  #dmatrix.dm-armed tbody tr{opacity:0;transform:translateY(7px)}
  #dmatrix.dm-armed tbody .dm-bar{transform:scaleX(0);transform-origin:left center}
  #dmatrix.dm-armed.dm-in tbody tr{opacity:1;transform:translateY(0);transition:opacity .5s var(--ease-draw),transform .5s var(--ease-draw);transition-delay:var(--dm-d,0s)}
  #dmatrix.dm-armed.dm-in tbody .dm-bar{transform:scaleX(1);transition:transform .6s var(--ease-bar);transition-delay:var(--dm-d,0s)}
  #dmatrix.dm-armed tbody tr:nth-child(1){--dm-d:.05s}
  #dmatrix.dm-armed tbody tr:nth-child(2){--dm-d:.11s}
  #dmatrix.dm-armed tbody tr:nth-child(3){--dm-d:.17s}
  #dmatrix.dm-armed tbody tr:nth-child(4){--dm-d:.23s}
  #dmatrix.dm-armed tbody tr:nth-child(5){--dm-d:.29s}
  #dmatrix.dm-armed tbody tr:nth-child(6){--dm-d:.35s}
  #dmatrix.dm-armed tbody tr:nth-child(7){--dm-d:.41s}
  #dmatrix.dm-armed tbody tr:nth-child(8){--dm-d:.47s}
  #dmatrix.dm-armed tbody tr:nth-child(9){--dm-d:.53s}
  #dmatrix.dm-armed tbody tr:nth-child(10){--dm-d:.59s}
}
#dmtip{position:fixed;z-index:60;pointer-events:none;display:none;opacity:0;transition:opacity .1s;max-width:270px;background:var(--text);color:#f4ede1;border-radius:6px;padding:10px 12px;font-family:var(--sans);font-size:12px;box-shadow:0 6px 22px rgba(0,0,0,.22)}
#dmtip .tn{font-family:var(--serif);font-size:14px;font-weight:600;color:#fff;margin-bottom:3px}
#dmtip .ts{font-family:var(--mono);font-size:11px;color:#e7c96b;margin-bottom:5px;line-height:1.4}
#dmtip .tt2{color:#d8cdbb;line-height:1.4}
/* PDF / print: the modality map is static on paper -- drop the sort affordances. */
@media print{
  .visual-frame .dm-hint{display:none !important}
  #dmatrix th .dm-caret{display:none !important}
}

  /* Stadium hybrid (§1) --------------------------------------------------- */
  .stadium-hybrid { position: relative; }
  .stadium-hybrid .stadium-img { width: 100%; display: block; border-radius: 4px; }
  .stadium-hybrid .stadium-ov { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
  .st-lab { position: absolute; transform: translate(-50%, -50%); background: rgba(255,253,249,0.93); border: 1px solid var(--line); border-left: 3px solid; border-radius: 6px; padding: 2px 8px; font-family: var(--sans); font-size: 11.5px; font-weight: 600; color: var(--text); white-space: nowrap; box-shadow: 0 1px 4px rgba(44,36,22,0.12); }
  .st-lab b { margin-right: 5px; }
  /* The watermark rides the entrance with the photo, so its resting opacity has
     to live here rather than inline: an inline declaration outranks the class
     rule that holds .sq-fade elements at zero before the reveal fires. */
  .stadium-hybrid .sa-wm { opacity: 0.45; }
  .stadium-readout { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
  .stadium-readout svg { width: 100%; height: auto; display: block; }
  /* Entrance. Only the three labels used to move, so the figure arrived mostly
     finished; now it assembles marker by marker, in the order the prose walks
     the stadium (1 outside the wall, 2 in the wall, 3 down in the crowd). The
     photo fades, then for each marker the pin lands, its leader line draws out
     from the pin toward the rim, and the label settles at the far end.
     Rides the shared .anim-seq engine: the stage carries the class, JS arms it
     only when it will actually animate, so no-JS / no-IO / reduced-motion / PDF
     readers get the finished figure instead of a blank one (the old
     .st-lab { opacity: 0 } had no such gate). Per-element timing is inline --d,
     the same idiom as .sq-fade / .sq-draw, which the photo and the three leader
     lines use directly.
     Pins and labels are anchored with transform: translate(-50%, -50%), so every
     keyframe here restates that translate; a bare scale() would survive as the
     filled final value and knock them off their anchor points. */
  @media (prefers-reduced-motion: no-preference) {
    .anim-seq.seq-armed .stadium-hybrid .sa-wm { opacity: 0; }
    .anim-seq.seq-armed .sa-pin { opacity: 0; pointer-events: none; }
    .anim-seq.seq-armed.seq-in .sa-pin {
      pointer-events: auto;
      animation: saPinIn 0.5s var(--ease-spring) both;
      animation-delay: var(--d, 0s);
    }
    @keyframes saPinIn {
      from { opacity: 0; transform: translate(-50%, -50%) scale(0.35); }
      to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    }
    .anim-seq.seq-armed .st-lab { opacity: 0; }
    .anim-seq.seq-armed.seq-in .st-lab {
      animation: saLabIn 0.45s var(--ease-draw) both;
      animation-delay: var(--d, 0s);
    }
    @keyframes saLabIn {
      from { opacity: 0; transform: translate(-50%, calc(-50% + 5px)); }
      to   { opacity: 1; transform: translate(-50%, -50%); }
    }
    /* The readout strip is display:none on hover-capable screens, so this only
       ever runs on touch, where the strip is the whole payload. It rises in
       after the three markers have landed. */
    .anim-seq.seq-armed .stadium-readout { opacity: 0; }
    .anim-seq.seq-armed.seq-in .stadium-readout {
      animation: saReadoutIn 0.6s var(--ease-draw) 1.3s both;
    }
    @keyframes saReadoutIn {
      from { opacity: 0; transform: translateY(9px); }
      to   { opacity: 1; transform: none; }
    }
  }
  /* Print: the figure must be whole whether or not the entrance ever fired.
     (The PDF flatten forces reduced motion, so it never arms; this covers a
     reader printing the live page from a figure they never scrolled past.) */
  @media print {
    .stadium-hybrid .sa-pin, .stadium-hybrid .st-lab, .stadium-hybrid .stadium-img, .stadium-readout { opacity: 1 !important; }
    .stadium-hybrid .sa-wm { opacity: 0.45 !important; }
    .stadium-ov line { opacity: 0.7 !important; stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
    .stadium-hybrid .sa-pin, .stadium-hybrid .st-lab, .stadium-hybrid .stadium-img,
    .stadium-hybrid .sa-wm, .stadium-ov line, .stadium-readout { animation: none !important; }
  }

  /* Stadium §1 pins: hover-to-hear on screen; readout strip is the touch/PDF fallback */
  .stadium-hybrid .pin { position: absolute; width: 36px; height: 36px; transform: translate(-50%, -50%); display: grid; place-items: center; z-index: 6; cursor: pointer; }
  .stadium-hybrid .pin::before { content: ""; position: absolute; inset: -12px; border-radius: 50%; }
  .stadium-hybrid .pin .dot { width: 25px; height: 25px; border-radius: 50%; background: var(--c); color: #fff; display: flex; align-items: center; justify-content: center; font-family: var(--sans); font-weight: 700; font-size: 12.5px; box-shadow: 0 1px 3px rgba(44,36,22,.35), 0 0 0 2px #fff; transition: box-shadow .16s; }
  .stadium-hybrid .pin:hover .dot { box-shadow: 0 1px 3px rgba(44,36,22,.35), 0 0 0 2px #fff, 0 0 0 6px var(--cr); }
  .stadium-hybrid .pin .tip { position: absolute; bottom: 130%; left: 50%; transform: translateX(-50%) translateY(4px); width: 172px; background: rgba(255,253,249,.98); border: 1px solid var(--line); border-top: 3px solid; border-radius: 9px; padding: 9px 11px 10px; box-shadow: 0 6px 20px rgba(44,36,22,.18); opacity: 0; visibility: hidden; transition: opacity .16s, transform .16s; pointer-events: none; z-index: 8; }
  .stadium-hybrid .pin:hover .tip { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
  /* Pins 1 & 2 hug the figure's left edge; their 172px tooltip, centred, clips
     ~35px off-screen on mobile (down to 320px). tip-right anchors it to open
     rightward so it stays fully in view. The tip-right class was already on the
     markup; this supplies the rule it was waiting for. */
  .stadium-hybrid .pin.tip-right .tip { transform: translateX(-24px) translateY(4px); }
  .stadium-hybrid .pin.tip-right:hover .tip { transform: translateX(-24px) translateY(0); }
  .stadium-hybrid .pin .tw { width: 100%; height: 34px; display: block; }
  .stadium-hybrid .pin .tn { font-family: var(--sans); font-weight: 700; font-size: 12.5px; margin-top: 5px; }
  .stadium-hybrid .pin .td { font-family: var(--sans); font-size: 11.5px; color: var(--muted); }
  /* §1 stadium waveform drift: waves rest still, and drift only while the figure is "live" (hover on pointer devices, in-view on touch). Ported from the redesign lab. */
  [class~="06-stadium-analogy-drift"],
  [class~="06-stadium-analogy-drift-tw"] { transform: translateX(0); will-change: transform; }
  @media (prefers-reduced-motion: no-preference) {
    [class~="06-stadium-analogy-live"] [class~="06-stadium-analogy-drift"] { animation: sa06drift 9s linear infinite; }
    [class~="06-stadium-analogy-live"] [class~="06-stadium-analogy-drift-tw"] { animation: sa06driftTw 7s linear infinite; }
    @keyframes sa06drift { from { transform: translateX(0); } to { transform: translateX(-174px); } }
    @keyframes sa06driftTw { from { transform: translateX(0); } to { transform: translateX(-138px); } }
  }
  .stadium-hint { position: absolute; top: 8px; right: 10px; font-family: var(--sans); font-size: 11px; color: var(--muted); background: rgba(255,253,249,.8); padding: 2px 8px; border-radius: 20px; z-index: 5; display: none; }
  /* Default (touch / no-hover): readout strip shown = static PDF variant. Hover-capable screens: hide strip, show hint = interactive variant. */
  @media (hover: hover) {
    .stadium-readout { display: none; }
    .stadium-hint { display: block; }
  }
  /* Wide screens: running text one point up. Column and every other size
     stay put; nothing below 1600px, and nothing in print, changes.
     Quiet-aside blockquotes ride along so they keep sitting a touch below
     body text optically: EB Garamond's x-height is 0.405em against Source
     Serif's 0.485em, so 19/17.5 here reproduces the 18/16.5 ratio the
     narrower widths already have (x-height 7.69px vs 8.43px, 0.91).
     Both selectors are needed: blockquote:not(.thesis) p outranks a bare
     blockquote rule, so the text-in-a-paragraph case needs its own line. */
  @media screen and (min-width: 1600px) {
    p { font-size: 17.5px; }
    ul li { font-size: 17px; }
    .the-bet p, .the-bet li { font-size: 17.5px !important; }
    blockquote:not(.thesis), blockquote:not(.thesis) p { font-size: 19px; }
  }

/* ---- figure motion-control chrome (from preview.html vrl-chrome) ---- */

/* Figure motion-control chrome: hover/tap cues */
.vrl-stage{position:relative}
.vrl-hint{position:absolute;right:12px;top:10px;display:flex;align-items:center;gap:7px;font-family:var(--sans,'Inter',sans-serif);font-size:9.5px;font-weight:600;letter-spacing:.06em;color:var(--muted);opacity:.6;transition:opacity .45s ease;pointer-events:none;z-index:4}
.vrl-hint[data-off]{opacity:0}
.vrl-ping{position:relative;width:8px;height:8px;flex:0 0 auto}
.vrl-ping::before{content:"";position:absolute;inset:2px;border-radius:50%;background:var(--accent-strong);opacity:.75}
.vrl-ping::after{content:"";position:absolute;inset:0;border-radius:50%;border:1px solid var(--accent-strong);animation:vrl-ping 2.6s ease-out infinite}
@keyframes vrl-ping{0%{transform:scale(.5);opacity:.7}70%{transform:scale(1.9);opacity:0}100%{transform:scale(1.9);opacity:0}}
@media (prefers-reduced-motion: reduce){
  .vrl-hint{display:none}
  .vrl-ping::after{animation:none}
}
@media (hover: none){
  .vrl-hint{display:none}
}
.vrl-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px}
.vrl-head .vis-label{margin-bottom:0}
/* The hint is a 2-4 word affordance, never prose: on a phone the long title wins
   the space contest and breaks "tap to compare" in half. Pin the hint and let the
   title wrap around it instead. A broken hint also drags itself out of line with
   the title, because this box's baseline comes from the empty ping dot. Same
   treatment as .dm-hint. */
.vrl-head .vrl-hint{position:static;white-space:nowrap}
.vrl-w-tap{display:none}
@media (hover: none){
  .vrl-head .vrl-hint.vrl-tapable{display:flex}
  .vrl-tapable .vrl-w-hover{display:none}
  .vrl-tapable .vrl-w-tap{display:inline}
}

/* ---- print: interactive controls that do nothing on paper ----
   These rules used to live only in pdf/make-spike-excerpt.py, which injects its own
   stylesheet over the flattened document. That made the PDF itself correct but left
   every other consumer of the essay's print CSS wrong. pdf/capture-figure-frames.mjs
   renders preview.html under emulateMedia('print') to show the review playground what
   each figure will actually print as, and it never sees the injected sheet: the
   playground was showing a live slider under the corpus-cost chart and the Timeline
   half of the predictions figure, neither of which is in the PDF. Owning the rules
   here makes the two agree by construction instead of by coincidence. The pipeline
   still carries its copy of them; it is now redundant rather than load-bearing.
   (25-08-2026.) */
@media print {
  /* corpus-cost slider: rescales the neural-corpus bar. On paper the chart renders its
     default ($300 per user-hour, so $3.0B, the same figure the prose beside it quotes),
     which leaves the control dead. The readout line and the comparison chips under it
     are the payload and stay. */
  [id="31-corpus-cost-a-rate"] { display: none !important; }
  /* Predictions overview: print takes the confidence-against-time scatter, not the
     default Timeline view (Sat, 12-08-2026). Both SVGs are fully drawn at init, so this
     is a visibility swap; !important beats the screen toggle rule #p2r .chartbox.hide. */
  #p2r-hero-timeline { display: none !important; }
  #p2r-hero-scatter  { display: block !important; }
  /* Page chrome with no paper equivalent: the scroll-progress bar, and the header
     "Last updated" chip (the printed header carries the byline only; version history
     lives on the About page). */
  .reading-progress, .last-updated { display: none !important; }
}

/* ---- A8 paragraph permalinks (from preview.html tail) ---- */
/* Watermark size, phone: the html stamps are plain CSS px so they do not
   scale with their figure the way the svg ones do, which left them at 8.5px
   on a phone while the svg stamp in the next figure sat at 4px. Clamp them
   into the same band the normalizer applies to svg (see the enhancement
   layer). This block is last in document order, which is what lets it beat
   the figure-local rules; media queries add no specificity. The iceberg
   stamp already has its own phone rule at 5.78px and is left alone. */
.sa-wm { font-size: 8.5px; }
@media (max-width: 640px) {
  .decode-figure .dw-wm, .reread-wm, #dmatrix .dm-wm, .pf34c-wm, .sa-wm { font-size: 7px; }
  /* stadium: a touch lower on a phone (Sat, 19-09-2026); !important beats the inline bottom:8px */
  .stadium-hybrid .sa-wm { bottom: 4px !important; }
}
article.essay > p{position:relative}
.pl-pmark{position:absolute;left:-1.5em;top:.28em;font-family:Georgia,serif;font-size:15.2px;
  color:var(--accent,#B8860B);opacity:0;cursor:pointer;text-decoration:none;user-select:none;transition:opacity .15s ease}
/* paragraph marks measured 3.7px below their first-line center at the base
   .28em; scoped correction so blockquote/pull-quote/figure tunings keep theirs */
article.essay > p > .pl-pmark{top:.04em}
article.essay > p:hover > .pl-pmark{opacity:.4}
.pl-pmark:hover{opacity:1}
/* click ack (set by mark()): opacity pin only, deliberately NO wash/pill/scale
   (Sat tried and removed each); the toast is the primary feedback. Held ~400ms then eased back by the mark's own
   transition; !important beats the per-container :hover ladder above without
   enumerating it */
.pl-pmark.pl-ack{opacity:1 !important;transition:opacity .12s ease}
/* slow fade only on the acknowledged path (class swapped in after the hold) */
.pl-pmark.pl-ack-out{transition:opacity .3s ease}
/* invisible hit-area extension: ~25x38px target (tablets take taps here) */
.pl-pmark::after{content:"";position:absolute;top:-6px;bottom:-6px;left:-9px;right:-9px}
article.essay > blockquote{position:relative}
article.essay > blockquote > .pl-pmark{top:1.1em}
article.essay > blockquote:hover > .pl-pmark{opacity:.4}
.pull-quote > .pl-pmark{top:.55em}
/* list items + checkpoint/story paragraphs (02-09-2026): same treatment as
   paragraphs. Lists are indented (1.6em margin + .4em padding) so their marks
   push further out to clear the bullet/number glyphs and land near the main
   paragraph column; the tinted .the-bet/.story-scene boxes keep theirs inside
   the box, clear of the 3px border-left. */
article.essay > ul > li,article.essay > ol > li,.the-bet > p,.the-bet li,.story-scene > p{position:relative}
article.essay > ul > li:hover > .pl-pmark,article.essay > ol > li:hover > .pl-pmark,.the-bet > p:hover > .pl-pmark,.the-bet li:hover > .pl-pmark,.story-scene > p:hover > .pl-pmark{opacity:.4}
article.essay > ul > li > .pl-pmark,article.essay > ol > li > .pl-pmark{left:-3.4em;top:.04em}
.the-bet > p > .pl-pmark{left:-1.2em;top:.04em}
.the-bet li > .pl-pmark{left:-2.5em;top:.04em}
.story-scene > p > .pl-pmark{left:-1.5em;top:.04em}
.visual-frame,.cine-plate{position:relative}
/* .pl-anchored is set by the figure pass on every container it anchors, so these
   rules cover .historical-plate and .brain-loop (added 24-08-2026) and whatever
   figure type comes next without this list having to grow again. Deliberately NOT
   set by mark(): paragraphs and blockquotes take the pl-arrive background wash on
   :target, not the figure ring, so the class stays scoped to figures.
   The .visual-frame,.cine-plate position:relative above is kept as-is: it predates
   the permalink pass and holds with JS disabled, where pl-anchored never lands. */
.pl-anchored{position:relative}
.pl-anchored:hover > .pl-pmark{opacity:.4}
/* the one full-bleed plate starts at the viewport edge: tuck its mark inside */
.cine-plate.wide > .pl-pmark{left:8px}
/* land arrival targets below ~5 lines of context: the glow marks the target,
   so pinning it to the exact viewport top is unnecessary and disorienting */
.pl-anchored[id],.visual-frame[id],.cine-plate[id],article.essay > blockquote[id],.pull-quote[id],article.essay > p[id],.essay h2[id],article.essay > ul > li[id],article.essay > ol > li[id],.the-bet > p[id],.the-bet li[id],.story-scene > p[id]{scroll-margin-top:96px}
/* Arrival glow: the receiving half of the ¶/# permalinks. Opening a copied
   link plays a one-shot brass wash on the target (150ms hold via delay +
   backwards fill, then a 1.5s fade; steady state untouched). The copier's own
   page never self-flashes: replaceState does not re-match :target. Figures
   get a ring instead of a fill (they already carry panel border + bg). */
/* the wash silhouette is the spread shadow's outer edge: css-backgrounds-3
   inflates its corners by s*(1+(r/s-1)^3), so with the 10px spread a declared
   6px rendered ~15px and 4px still ~12px. 1px lands the visible corner at
   ~4px while keeping the full 10px of air. Declared inside the keyframes so
   it exists only while the wash is visible and never rounds the blockquote
   border or heading hairline at rest */
@keyframes pl-arrive{from{background-color:rgba(184,134,11,.18);box-shadow:0 0 0 10px rgba(184,134,11,.18);border-radius:1px}to{background-color:transparent;box-shadow:0 0 0 10px rgba(184,134,11,0);border-radius:1px}}
article.essay > p:target,article.essay > blockquote:target,.essay h2:target,article.essay > ul > li:target,article.essay > ol > li:target,.the-bet > p:target,.the-bet li:target,.story-scene > p:target{animation:pl-arrive 1.5s ease-out .15s backwards}
@keyframes pl-arrive-ring{from{outline-color:rgba(184,134,11,.4)}to{outline-color:rgba(184,134,11,0)}}
.pl-anchored:target{outline:3px solid transparent;outline-offset:2px;animation:pl-arrive-ring 1.5s ease-out .15s backwards}
@media (prefers-reduced-motion:reduce){
article.essay > p:target,article.essay > blockquote:target,.essay h2:target,.pl-anchored:target,article.essay > ul > li:target,article.essay > ol > li:target,.the-bet > p:target,.the-bet li:target,.story-scene > p:target{animation:none}
}
@media(max-width:1100px){.pl-pmark{left:-1.15em}
article.essay > ul > li > .pl-pmark,article.essay > ol > li > .pl-pmark{left:-3.05em}
.the-bet > p > .pl-pmark{left:-1.2em}
.the-bet li > .pl-pmark{left:-2.5em}
.story-scene > p > .pl-pmark{left:-1.5em}}
@media(max-width:720px){.pl-pmark{display:none}}

/* The bottom list is a content source for the sidenote/popover cloner, never a rendered
   section: >=1200px the gutter sidenote serves, 641-1199px the footnote popover serves,
   <=640px preview.html already hides it in favour of the tap bubble. Keep the markup: the
   cloner builds fnMap from `.footnotes [id^="fn-"]` and it is the only source of note text. */
@media (min-width: 641px) { .footnotes { display: none; } }

/* ================= Phase B design layer (ported from page-format-playground.html) ================= */

h2[id], .chapter-plate[id], .visual-frame[id], .cine-plate[id], article.essay > blockquote[id], .pull-quote[id] { scroll-margin-top: 24px; }

/* Nav-jump wash gate (class set by the head inline script when the arrival
   hash was stamped by a nav click): suppress the :target permalink wash.
   !important because the pl-arrive rules in the content CSS carry higher
   specificity (article.essay > p:target). Lifted on the first hashchange. */
html.pl-no-arrive :target{animation:none !important}

/* ---- fixed section index, left gutter (nav-essayindex) ---- */
.nav-essayindex{position:fixed;left:18px;top:24px;bottom:12px;width:200px;overflow-y:auto;font-family:var(--sans);font-size:11.5px;z-index:24;padding-right:6px}
.nav-essayindex::-webkit-scrollbar{width:4px}
.nav-essayindex::-webkit-scrollbar-thumb{background:var(--line);border-radius:2px}
.nav-essayindex a{display:block;text-decoration:none;border-bottom:none}
.nav-essayindex .np-part{font-size:10.5px;font-weight:500;text-transform:uppercase;letter-spacing:0.14em;color:color-mix(in srgb,var(--accent) 62%,var(--text-2));padding:12px 0 5px 10px;border-left:2px solid transparent;margin-left:-2px;cursor:pointer;transition:color .15s ease}
.nav-essayindex .np-part:hover{color:var(--accent)}
.nav-essayindex .np-part.cur{color:var(--accent);font-weight:700;border-left-color:var(--accent)}
.nav-essayindex .np-group:first-of-type .np-part{padding-top:2px}
.nav-essayindex .np-sec{padding:4px 0 4px 10px;color:var(--muted);cursor:pointer;border-left:2px solid transparent;margin-left:-2px;line-height:1.35;transition:color .2s,border-color .2s}
.nav-essayindex .np-sec:hover{color:var(--text)}
.nav-essayindex .np-sec.cur{color:var(--accent-strong);border-left-color:var(--accent);font-weight:600}
.nav-essayindex .np-sec .n{font-family:var(--mono);font-size:10px;color:var(--dim);margin-right:6px}
.nav-essayindex .np-end{font-size:10.5px;font-weight:500;text-transform:uppercase;letter-spacing:0.14em;color:var(--muted);padding:12px 0 5px 10px;cursor:pointer;border-top:1px solid var(--line);margin-top:14px;transition:color .15s ease}
.nav-essayindex .np-end:hover{color:var(--accent)}
.nav-essayindex .np-end.cur{font-weight:700}
.nav-essayindex .np-end + .np-end{border-top:none;margin-top:0;padding-top:4px}
.nav-essayindex .np-pdf{font-size:9.5px;letter-spacing:0.12em;color:var(--dim)}
.nav-unfold .np-secs{display:grid;grid-template-rows:1fr;transition:grid-template-rows .3s var(--ease-draw)}
.nav-unfold .np-secs-i{min-height:0;overflow:hidden;transition:opacity .25s var(--ease-draw),transform .3s var(--ease-draw),visibility 0s .3s}
.nav-unfold .np-group:not(.cur) .np-secs{grid-template-rows:0fr}
.nav-unfold .np-group:not(.cur) .np-secs-i{opacity:0;transform:translateY(-3px);visibility:hidden}
.nav-unfold .np-group:not(.cur):hover .np-secs{grid-template-rows:1fr;transition-delay:.12s}
.nav-unfold .np-group:not(.cur):hover .np-secs-i{opacity:1;transform:none;visibility:visible;transition-delay:.12s}
.nav-unfold .np-group:not(.cur):focus-within .np-secs{grid-template-rows:1fr;transition-delay:0s}
.nav-unfold .np-group:not(.cur):focus-within .np-secs-i{opacity:1;transform:none;visibility:visible;transition-delay:0s}
@media (prefers-reduced-motion: reduce){
  .nav-unfold .np-secs,.nav-unfold .np-secs-i{transition:none}
}
@media (max-width: 1380px){ .nav-essayindex{display:none} }

/* ---- mobile / narrow drawer (top-right card button + fullscreen overlay) ---- */
.nav-drawer-btn.ndb-topright{position:fixed;top:12px;right:12px;z-index:46;width:40px;height:40px;border-radius:8px;background:var(--bg-2);color:var(--accent);border:1px solid var(--line);cursor:pointer;font-size:20px;box-shadow:0 4px 14px var(--line);display:none;align-items:center;justify-content:center;padding:0}
.nav-drawer-btn.ndb-topright.open{background:var(--accent);color:#fff;border-color:var(--accent)}
@media (max-width: 1380px){ .nav-drawer-btn.ndb-topright{display:flex} }
.nav-drawer{position:fixed;inset:0;z-index:45;background:rgba(20,14,4,0.5);display:none}
.nav-drawer.open{display:block}
.nav-drawer .nd-panel{position:absolute;inset:0;width:100%;max-width:none;height:100%;background:var(--bg);padding:28px 24px 32px;overflow-y:auto;box-shadow:none}
.nd-panel a{display:block;text-decoration:none;border-bottom:none}
.nd-panel .np-title{font-family:var(--sans);font-size:9.5px;text-transform:uppercase;letter-spacing:0.16em;color:var(--muted);font-weight:600;margin:0 4px 16px}
.nd-panel .np-part{font-family:var(--serif);font-size:16px;font-weight:700;color:var(--text);padding:12px 4px 5px;cursor:pointer}
/* The Part heading keeps its brass on every page, alongside the washed section row.
   The two cues answer different questions (which Part, which section), and the wash
   now carries the weight, so the brass no longer has to do the job on its own. On
   its own it could not: brass is *lighter* (4.76:1 against the cream) than the
   --text the other Part headings are set in (14.33:1). Reading second, next to the
   wash, it lands as a hue change rather than as lost emphasis. */
.nd-panel .np-part.cur{color:var(--accent-strong)}
.nd-panel .np-sec{font-family:var(--sans);font-size:13.5px;color:var(--text-2);padding:7px 4px 7px 18px;cursor:pointer;line-height:1.4}
/* The current row is a brass wash bleeding the full width of the panel, with the
   title darker and heavier than the rows around it. Colour alone did not carry it:
   --accent-strong sits at 4.76:1 against the cream while an ordinary row sits at
   7.61:1, so marking the row in brass made it the *lightest* text in a flat list of
   32, which read as no cue at all on a phone. The negative margins reach the screen
   edge; the padding compensates by exactly as much, so the title never shifts
   sideways as the spy moves the cue from row to row. */
.nd-panel .np-sec.cur{margin:0 -24px;padding-left:42px;padding-right:28px;background:var(--accent-soft);color:var(--text);font-weight:600}
.nd-panel .np-sec .n{font-family:var(--mono);font-size:10.5px;color:var(--dim);margin-right:8px}
.nd-panel .np-sec.cur .n{color:var(--accent)}
.nd-panel .np-end{font-family:var(--sans);font-size:14px;color:var(--text-2);padding:14px 4px 5px;cursor:pointer;border-top:1px solid var(--line);margin-top:14px}
.nd-panel .np-end.cur{color:var(--accent-strong);font-weight:600}
.nd-panel .np-end + .np-end{border-top:none;margin-top:0;padding-top:7px}
body.drawer-open{overflow:hidden}

/* ---- cross-page nav fade (touch): instant cream response while the next page loads ---- */
html.pl-navigating::after{content:"";position:fixed;inset:0;z-index:400;background:var(--bg);opacity:1;transition:opacity .18s ease}
@starting-style{html.pl-navigating::after{opacity:0}}

/* ---- chapter plate as page opener (opener-essayplate values) ---- */
.chapter-plate{border-top:none;border-bottom:none;margin:30px 0 30px;padding:8px 0 12px}
.chapter-plate .plate-img{max-width:300px;border-radius:5px;opacity:0.94;margin:0 auto 24px}

/* ---- end matter: prev/next arrows (no hairline: border-top dropped 02-08-2026) ---- */
.endmatter{margin-top:48px;padding-top:28px;border-top:1px solid var(--line)}
.pn-arrows{display:flex;justify-content:space-between;align-items:center;gap:18px;margin-bottom:24px;font-family:var(--serif);font-size:15px;font-weight:600}
.pn-arrows .pna,.pn-arrows a.pna{color:var(--text-2);cursor:pointer;transition:color .15s ease;text-decoration:none;border-bottom:none;background:none}
.pn-arrows .pna:hover,.pn-arrows a.pna:hover{color:var(--accent-strong)}
.pn-arrows .pna .arw{color:inherit}

/* ---- date line + version-history popover (Executive Summary) ---- */
.dateline{display:inline-block;margin-top:0}
.dateline::before{content:"\00B7";margin:0 8px;color:var(--dim)}
.dt-hoverline{font-family:var(--sans);font-size:12px;color:var(--muted);position:relative;cursor:pointer;display:inline-block}
.dt-hoverline .lu-word{color:var(--muted);font-weight:400;margin-right:4px;font-size:12px;text-transform:none;letter-spacing:0.02em}
.dt-hoverline .dtv{color:var(--muted);border-bottom:1px dotted color-mix(in srgb,var(--accent) 50%,transparent);transition:color .15s ease}
.dt-hoverline:hover .dtv{color:var(--accent-strong);border-bottom-color:var(--accent)}
.dt-hoverline.open .clog-pop,.dt-hoverline:hover .clog-pop{display:block}
.clog-pop{display:none;position:absolute;width:255px;background:var(--bg-2);border:1px solid var(--line);border-radius:10px;padding:12px 14px;box-shadow:0 10px 28px color-mix(in srgb,var(--text) 18%,transparent);z-index:50;text-align:left}
.dt-hoverline .clog-pop{top:100%;left:0;transform:none;margin-top:7px}
.dt-hoverline .clog-pop::before{content:"";position:absolute;top:-9px;left:0;right:0;height:9px}
.clog-pop .cl-h{display:block;font-family:var(--sans);font-size:9.5px;text-transform:uppercase;letter-spacing:0.13em;color:var(--muted);font-weight:600;margin-bottom:8px}
.clog-pop .cl-r{display:flex;gap:10px;font-family:var(--sans);font-size:11.5px;color:var(--text-2);padding:3px 0;line-height:1.45}
.clog-pop .cl-d{font-family:var(--mono);font-size:10px;color:var(--accent-strong);flex:0 0 auto;padding-top:1px}
@media (max-width:480px){.dt-hoverline .clog-pop{left:auto;right:0}}

/* ---- thesis box (playground restyle) ---- */
blockquote.thesis{border:1px solid color-mix(in srgb,var(--accent) 18%,transparent);background:linear-gradient(180deg,color-mix(in srgb,var(--accent) 5%,transparent),color-mix(in srgb,var(--accent) 2%,transparent));border-radius:10px;padding:22px 22px 18px}

/* ---- resume chip ("pick up where you left off"): fixed pill, bottom center ---- */
.resume-chip{position:fixed;left:50%;bottom:calc(18px + env(safe-area-inset-bottom,0px));transform:translate(-50%,8px);display:flex;align-items:center;max-width:min(92vw,480px);background:var(--bg-2);border:1px solid color-mix(in srgb,var(--accent) 30%,var(--line));border-radius:999px;box-shadow:0 10px 28px color-mix(in srgb,var(--text) 18%,transparent);z-index:40;opacity:0;pointer-events:none;transition:opacity .25s ease,transform .25s ease}
.resume-chip.show{opacity:1;transform:translate(-50%,0);pointer-events:auto}
.resume-chip .rc-link{display:flex;align-items:baseline;gap:8px;min-width:0;padding:10px 2px 10px 16px;font-family:var(--sans);font-size:12.5px;color:var(--text-2);text-decoration:none;border-bottom:none}
.resume-chip .rc-k{flex:0 0 auto;font-size:9.5px;font-weight:600;text-transform:uppercase;letter-spacing:0.13em;color:var(--accent)}
.resume-chip .rc-t{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.resume-chip .rc-link:hover .rc-t{color:var(--accent-strong)}
.resume-chip .rc-x{flex:0 0 auto;width:30px;height:30px;margin-right:5px;border:none;background:none;color:var(--muted);cursor:pointer;border-radius:50%;display:flex;align-items:center;justify-content:center;padding:0}
.resume-chip .rc-x:hover{color:var(--text);background:color-mix(in srgb,var(--text) 8%,transparent)}
.resume-chip .rc-link:focus-visible,.resume-chip .rc-x:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:999px}
@media (max-width:640px){.resume-chip{max-width:calc(100vw - 24px)}}
@media (prefers-reduced-motion: reduce){.resume-chip{transition:none}}
@media print{.resume-chip{display:none}}

/* FOUC guard release (see head_template): CSS parse restores visibility;
   the head script restores opacity on fonts-ready (or its 400ms cap).
   Opacity must NOT be set here -- that would bypass the font gate. */
html { visibility: visible; transition: opacity .18s ease; }
