:root {
  color-scheme: dark;
  /* Theme machinery overrides --c1..c5, --accent, --accent-soft via
     JS. The fallback values below match the previous hard-coded
     palette so the page looks identical on a fresh load. */
  --theme-l: 70%;
  --theme-c: 0.15;
  --theme-h: 250;
  --c1: oklch(70% 0.15 250);
  --c2: oklch(70% 0.15 220);
  --c3: oklch(70% 0.15 280);
  --c4: oklch(70% 0.15 340);
  --c5: oklch(70% 0.15 100);
  --accent: var(--c1);
  --accent-soft: oklch(70% 0.15 250 / 0.15);

  --bg: #07090d;
  --bg-rgb: 7, 9, 13;
  --bg-elev: #0d1117;
  --bg-elev2: #161b22;

  /* Text & line greys, theme-aware with a discontinuous jump.

     A *linear* response of grey-L to theme-L is too easy to push
     into unreadable territory at the slider extremes. So the L
     dependence is a STEP at theme-L = 50%: clamp(0, (L - 50) ×
     1000, 1) gives 0 below 50 and 1 above 50 with a vanishingly
     small transition zone — effectively a step function expressed
     in pure CSS. Both step values are inside their grey's safe
     contrast band. Chroma tint stays linear (high C → more hue
     in the greys) since chroma doesn't threaten contrast. Falls
     back to mid-band values if --theme-l-num isn't set (briefly
     during initial load before JS runs).

     The step landing zones are calibrated so:
       theme-L < 50 → cooler / dimmer greys (suits darker accents)
       theme-L ≥ 50 → brighter / fuller greys (suits brighter accents)
     and the difference is small enough not to wreck the design,
     yet large enough that you SEE the click as you cross 50%. */
  --l-step: clamp(0, calc((var(--theme-l-num, 70) - 50) * 1000), 1);
  --c-tint: calc(var(--theme-c, 0.15) * 0.18);

  --fg: oklch(
    calc(89% + var(--l-step) * 5%)
    var(--c-tint)
    var(--theme-h, 250));
  --fg-dim: oklch(
    calc(56% + var(--l-step) * 9%)
    calc(var(--c-tint) * 1.2)
    var(--theme-h, 250));
  --fg-mute: oklch(
    calc(33% + var(--l-step) * 7%)
    calc(var(--c-tint) * 0.9)
    var(--theme-h, 250));
  --line: oklch(
    calc(16% + var(--l-step) * 4%)
    calc(var(--c-tint) * 0.4)
    var(--theme-h, 250));
  --line-soft: oklch(
    calc(20% + var(--l-step) * 4%)
    calc(var(--c-tint) * 0.5)
    var(--theme-h, 250));
  --accent: #58a6ff;
  --accent-soft: rgba(88, 166, 255, 0.15);
  --warn: #f85149;
  --ok: #3fb950;
  --phase-clean: #3fb950;
  --phase-armed: #d29922;
  --phase-infecting: #db61a2;
  --phase-running: #f85149;
  --phase-dormant: #6e7681;
  --topbar-h: 44px;
  --prose-w: 36em;
  --scene-fade-ms: 600ms;
}

* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; background: var(--bg); color: var(--fg);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased; overflow-x: hidden;
}

/* ─── Background canvas (theme-driven) ─────────────────────────────── */
/* Animation timings respect --anim-speed (multiplier; 1 = nominal,
   2 = twice as fast). Whole canvas can also be blurred via
   --bg-blur. Both are set by the theme panel sliders. */
/* Minimal positioning, no layer-promotion tricks. Every stacking-
   context-creating property here (filter, transform, isolation,
   contain) caused some downstream compositor artifact in earlier
   iterations. The browser will GPU-promote on its own when needed.
   `filter` is only applied when the bg-blur slider is non-zero —
   the JS sets `--bg-filter` to `blur(Npx)` then, and removes the
   custom property when blur is 0 so the rule falls through to
   `none`. */
.bg-canvas {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
  background: var(--bg);
  filter: var(--bg-filter, none);
  overflow-anchor: none;
}

/* Subtle palette-tinted vignette — applies on every theme so even
   "black" picks up palette character. Tint strength controlled by
   --tint-strength via the panel. */
.bg-tint {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(ellipse 90% 70% at 50% 65%,
      oklch(var(--theme-l) calc(var(--theme-c) * 0.6) var(--theme-h) / var(--tint-strength, 0.10)),
      transparent 65%);
}

.bg-drift, .bg-lava, .bg-vaporwave, .bg-laser {
  position: absolute; inset: 0; display: none;
}
body[data-theme="drift"] .bg-drift         { display: block; }
body[data-theme="lava"]  .bg-lava          { display: block; }
body[data-theme="vaporwave"] .bg-vaporwave { display: block; }
body[data-theme="laser"] .bg-laser         { display: block; }

/* drift — soft, blurred radial blobs floating up and back down.
   The blobs are JS-generated; styling here uses theme variables
   (--drift-blur, --drift-opacity) so live slider changes don't
   need a DOM rebuild. */
.drift-blob {
  position: absolute; border-radius: 50%;
  filter: blur(var(--drift-blur, 70px));
  opacity: var(--drift-opacity, 0.55);
  mix-blend-mode: screen;
}
@keyframes drift-rise {
  0%, 100% { transform: translateY(0) scale(1); }
  50%      { transform: translateY(-130vh) scale(1.4); }
}

/* lava — metaballs via SVG goo filter. The filter values
   (stdDeviation, alpha-threshold matrix entries) are updated by
   JS as the goo strength / blur sliders move. Bubbles are
   JS-generated so their statistical-distribution sliders work. */
.goo-container { position: absolute; inset: 0; filter: url(#goo); }
.goo-bubble    { position: absolute; border-radius: 50%; opacity: 0.95; }
@keyframes goo-rise {
  0%   { transform: translateY(0)     scale(0.9); }
  45%  { transform: translateY(-65vh) scale(1.05); }
  55%  { transform: translateY(-75vh) scale(1.1);  }
  100% { transform: translateY(-130vh) scale(0.85); }
}

/* ─── Vaporwave (overhauled) ───────────────────────────────────────
   Layered: gradient sky → palette-blended sun with venetian-blind
   stripes → glowing horizon → perspective grid floor → CRT scanlines.
   Knobs (CSS vars set by sliders): --vw-horizon, --vw-grid-size,
   --vw-perspective, --vw-sun-size, --vw-blind. */
.bg-vaporwave {
  position: absolute; inset: 0;
  background: radial-gradient(
    ellipse 100% 70% at 50% var(--vw-horizon, 55%),
    transparent 0%, transparent 60%,
    rgba(0, 0, 0, 0.6) 100%);
}
.vw-sky {
  position: absolute; left: 0; right: 0; top: 0;
  height: var(--vw-horizon, 55%);
  background: linear-gradient(
    to bottom,
    oklch(15% 0.20 var(--theme-h, 250)) 0%,
    var(--c1) 60%,
    var(--c2) 100%);
}
.vw-sun {
  position: absolute; left: 50%;
  top: var(--vw-horizon, 55%);
  width: var(--vw-sun-size, 50vmin);
  height: var(--vw-sun-size, 50vmin);
  transform: translate(-50%, -78%);
  border-radius: 50%;
  background: linear-gradient(
    to bottom,
    var(--c3) 0%,
    var(--c4) 50%,
    var(--c5) 100%);
  overflow: hidden;
  box-shadow: 0 0 80px var(--c4), 0 0 160px var(--c3);
}
/* Venetian-blind stripes on the lower 50% — black-bg occluders, so
   the sun's gradient peeks through the gaps. */
.vw-sun-blinds {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 50%;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0 calc(var(--vw-blind, 11px) - 4px),
    rgba(0, 0, 0, 0.92) calc(var(--vw-blind, 11px) - 4px) var(--vw-blind, 11px));
}
.vw-horizon {
  position: absolute; left: 0; right: 0;
  top: var(--vw-horizon, 55%);
  height: 1px; background: var(--c1);
  box-shadow:
    0 0 20px var(--c1),
    0 0 40px var(--c1),
    0 0 80px var(--c2);
}
/* The vaporwave floor is a <canvas> drawn frame-by-frame in JS via
   requestAnimationFrame. Two earlier CSS-only attempts (perspective +
   rotateX with animated background-position; then split rotate +
   translate3d on a repeating-linear-gradient) both flickered when
   the user scrolled — the perspective math kept getting recomputed
   in lockstep with document scroll, even with isolation: isolate
   and contain: paint. The canvas approach side-steps that entirely:
   no perspective transform, lines drawn at exact pixel positions
   each frame, palette colors read from CSS custom properties. */
/* CSS perspective grid. Three layered elements so rotate (static)
   and translate (animated) live on different layers — animating
   transform on the rotated element directly was what re-rasterized
   per frame in earlier attempts. Each element is also pushed into
   its own compositor layer to keep the document scroll from
   dirtying the bg. */
/* Collapsed structure: one perspective container, one animated grid
   element. The rotation and translation live on the SAME transform
   on the SAME element — no nested transform-style: preserve-3d, no
   separately-promoted compositor layer for the translate. The
   compositor has nothing to disagree about, so the phantom-orthogonal
   3D-grid post artifact disappears. */
.vw-floor {
  position: absolute; left: -50%; right: -50%;
  top: var(--vw-horizon, 55%); bottom: -10%;
  perspective: 800px;
  overflow: hidden;
}
.vw-floor-grid {
  position: absolute; left: 0; right: 0; top: 0; height: 200%;
  background-image:
    repeating-linear-gradient(
      to bottom,
      transparent 0 calc(var(--vw-grid-size, 80px) - 3px),
      var(--c1)   calc(var(--vw-grid-size, 80px) - 3px) var(--vw-grid-size, 80px)),
    repeating-linear-gradient(
      to right,
      transparent 0 calc(var(--vw-grid-size, 80px) - 3px),
      var(--c2)   calc(var(--vw-grid-size, 80px) - 3px) var(--vw-grid-size, 80px));
  transform-origin: top center;
  animation: vw-floor-y calc(4s / var(--anim-speed, 1)) linear infinite;
}
/* Both keyframes carry the same rotateX, so it stays static; only
   translateY interpolates. The grid never leaves its rotated plane
   because its rotation is part of the same transform list. */
@keyframes vw-floor-y {
  from { transform: rotateX(var(--vw-perspective, 62deg)) translateY(0); }
  to   { transform: rotateX(var(--vw-perspective, 62deg))
                    translateY(var(--vw-grid-size, 80px)); }
}
/* Scanlines: confined to the sky above the horizon and stacked
   below the sun (DOM-ordered between .vw-sky and .vw-sun) so the
   sun's solid disc occludes them in its area. The 5 px period is
   off-resonance with the floor's perspective grid (which is below
   the horizon and never overlaps with these anyway) and with the
   sun's blinds (which the sun itself hides). Plain alpha
   compositing — no mix-blend-mode multiply that previously
   created an isolation group flattening 3D content beneath. */
.vw-scanlines {
  position: absolute; left: 0; right: 0; top: 0;
  height: var(--vw-horizon, 55%);
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0 3px,
    rgba(0, 0, 0, 0.14) 3px 5px);
}


/* laser show — long beams rotating from screen center, palette colors.
   Beams JS-generated so count/thickness/blur sliders work. */
.laser-beam {
  position: absolute; left: 50%; top: 50%;
  height: var(--laser-thickness, 3px);
  width: 200vmax; transform-origin: left center;
  filter: blur(var(--laser-blur, 2px));
  opacity: var(--laser-opacity, 0.55);
  mix-blend-mode: screen;
}
@keyframes laser-sweep {
  from { transform: rotate(0deg);   }
  to   { transform: rotate(360deg); }
}

/* ─── References scene (PDF viewer + tab strip + description) ──────── */
.ref-stack { /* metric-stack-wide variant; let content area fill height */
  height: 100%;
  justify-content: flex-start;
}
.ref-tabs {
  display: flex; flex-wrap: wrap; gap: 6px;
  max-height: clamp(60px, 9vh, 110px);
  overflow-y: auto;
}
.ref-tabs .awaiting {
  color: var(--fg-mute); font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 4px 0;
}
.ref-tab {
  background: transparent; color: var(--fg-dim);
  border: 1px solid var(--line); border-radius: 16px;
  padding: 4px 12px; font: inherit; font-size: 12px; cursor: pointer;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  transition: color 120ms, border-color 120ms, background 120ms;
  max-width: 28em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ref-tab:hover { color: var(--fg); border-color: var(--fg-mute); }
.ref-tab.active {
  color: var(--accent); border-color: var(--accent);
  background: var(--accent-soft);
}
/* Two-column layout: PDF viewer on the left taking the larger
   share, description panel on the right. The viewer's column
   uses minmax(0, …) so the iframe won't blow out the grid when
   the PDF reports a wide intrinsic size. */
.ref-content {
  flex: 1 1 auto; min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(280px, 0.55fr);
  gap: 14px;
}
.ref-viewer-wrap {
  background: var(--bg-elev);
  border: 1px solid var(--line); border-radius: 4px;
  overflow: hidden;
  min-height: 0;
}
.ref-viewer {
  width: 100%; height: 100%;
  min-height: clamp(360px, 70vh, 900px);
  border: 0; display: block; background: var(--bg-elev);
}
.ref-description {
  background: var(--bg-elev);
  border: 1px solid var(--line); border-radius: 4px;
  overflow-y: auto;
  padding: 18px 22px;
  font-size: 14px; line-height: 1.6;
  color: var(--fg);
  min-height: 0;
}
.ref-description h1, .ref-description h2 {
  font-size: 15px; font-weight: 600; margin: 0 0 10px;
  color: var(--fg);
}
.ref-description h3 { font-size: 13px; font-weight: 600; margin: 12px 0 4px; }
.ref-description p  { margin: 0 0 10px; }
.ref-description ul,
.ref-description ol { margin: 0 0 10px; padding-left: 20px; }
.ref-description li { margin: 0 0 4px; }
.ref-description code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em; color: var(--accent);
  background: var(--accent-soft); padding: 1px 5px; border-radius: 3px;
}
.ref-description strong { color: var(--fg); font-weight: 600; }
.ref-description em     { color: var(--fg-dim); font-style: italic; }
.ref-description .awaiting {
  color: var(--fg-mute); font-style: italic;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
}

/* On narrow viewports stack vertically: PDF on top, description
   below, capped to a sensible height so the PDF still gets room. */
@media (max-width: 1100px) {
  .ref-content { grid-template-columns: 1fr; }
  .ref-description { max-height: 240px; }
}

/* References scene wants more horizontal room than the default
   metric scenes — the PDF is the point. Drop the right padding
   that reserves space for the prose column. The prose for this
   scene is hidden anyway (see below) so we can use the full width
   for the PDF + description grid. */
.stage-view[data-view="references"] {
  padding-right: clamp(8px, 2vw, 48px);
}
/* Hide the prose card on the references scene — the description
   panel inside the metric-stack already explains each PDF in
   context, and freeing the right-side viewport gives the
   description panel proper room. */
.scene[data-stage="references"] .prose { display: none; }

/* ─── Per-theme settings section ───────────────────────────────────── */
.theme-bg-section { display: none; }
.theme-bg-section.is-active { display: block; }

/* Continuous-harmony hint line */
.theme-harmony-hint {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--fg-mute); padding: 2px 0;
}

/* Theme-panel advanced section accordion */
.theme-advanced { border-top: 1px solid var(--line); padding-top: 6px; }
.theme-advanced > summary {
  cursor: pointer; color: var(--fg-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; letter-spacing: 0.04em; padding: 4px 0;
  list-style: none;
}
.theme-advanced > summary::-webkit-details-marker { display: none; }
.theme-advanced > summary::before { content: '▸ '; display: inline-block; transition: transform 150ms; }
.theme-advanced[open] > summary::before { transform: rotate(90deg); }
.theme-advanced > .theme-sliders { padding-top: 6px; }

/* ─── Theme panel ──────────────────────────────────────────────────── */
/* Right-half sidebar that slides in from the right when open.
   Always rendered (no hidden attribute) so the transform animation
   works; visibility gated by the .is-open class. Pointer-events
   are turned off when closed so the hidden panel doesn't intercept
   clicks on the page beneath. */
.theme-panel {
  position: fixed;
  top: var(--topbar-h); right: 0; bottom: 0;
  width: 50vw;
  z-index: 60;
  background: rgba(13, 17, 23, 0.95);
  backdrop-filter: blur(12px);
  border-left: 1px solid var(--line);
  padding: 20px 28px 28px;
  color: var(--fg); font-size: 12px;
  display: flex; flex-direction: column; gap: 14px;
  overflow-y: auto;
  box-shadow: -20px 0 60px rgba(0, 0, 0, 0.5);
  transform: translateX(100%);
  transition: transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
  pointer-events: none;
}
.theme-panel.is-open {
  transform: translateX(0);
  pointer-events: auto;
}
@media (max-width: 880px) {
  .theme-panel { width: 100vw; }
}
.theme-panel-header {
  position: sticky; top: 0;
  display: flex; align-items: center; gap: 8px;
  background: inherit;
  padding-bottom: 10px;
  margin: -20px -28px 0;
  padding: 14px 28px;
  border-bottom: 1px solid var(--line);
}
.theme-panel-header .theme-title { flex: 1; font-weight: 600; letter-spacing: 0.04em; }
.theme-panel select, .theme-panel input[type="range"] {
  background: var(--bg-elev); color: var(--fg);
  border: 1px solid var(--line); border-radius: 4px;
  font: inherit; font-size: 12px;
}
.theme-panel select { padding: 4px 8px; }
.theme-row {
  display: flex; align-items: center; gap: 10px;
}
.theme-row > span:first-child {
  flex: 0 0 90px; color: var(--fg-dim);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; letter-spacing: 0.04em; text-transform: lowercase;
}
.theme-row > select { flex: 1; }

.theme-wheel-block {
  display: grid; grid-template-columns: 200px 1fr; gap: 14px; align-items: center;
}
.theme-wheel {
  position: relative; width: 200px; height: 200px;
  user-select: none; touch-action: none;
}
.wheel-disc {
  position: absolute; inset: 0; border-radius: 50%;
  /* Conic starts from 0deg = 12 o'clock (top). Was previously
     `from -90deg` which puts the gradient origin at 9 o'clock —
     a 90° offset from where the JS marker code expects it
     (positionMarker uses (H - 90)°, so H=0 lands at the top). */
  background: conic-gradient(
    from 0deg,
    oklch(var(--theme-l) var(--theme-c) 0),
    oklch(var(--theme-l) var(--theme-c) 60),
    oklch(var(--theme-l) var(--theme-c) 120),
    oklch(var(--theme-l) var(--theme-c) 180),
    oklch(var(--theme-l) var(--theme-c) 240),
    oklch(var(--theme-l) var(--theme-c) 300),
    oklch(var(--theme-l) var(--theme-c) 360)
  );
}
.wheel-rim {
  position: absolute; inset: 25%; border-radius: 50%;
  background: var(--bg-elev); border: 1px solid var(--line);
}
.wheel-markers { position: absolute; inset: 0; pointer-events: none; }
.wheel-marker {
  position: absolute; width: 18px; height: 18px;
  border-radius: 50%; border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
  transform: translate(-50%, -50%); cursor: grab;
  pointer-events: auto; touch-action: none;
}
.wheel-marker.primary {
  width: 24px; height: 24px;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6), 0 0 12px rgba(255, 255, 255, 0.3);
}
.wheel-marker:active, .wheel-marker.dragging { cursor: grabbing; }

.theme-sliders {
  display: flex; flex-direction: column; gap: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--fg-dim);
}
.theme-sliders label { display: flex; flex-direction: column; gap: 3px; }
.theme-sliders input[type="range"] { width: 100%; }

.theme-swatches {
  flex: 1; display: flex; gap: 4px; height: 28px;
}
.theme-swatch {
  flex: 1; border-radius: 3px; border: 1px solid var(--line);
}

.theme-meta-row {
  display: flex; align-items: center; justify-content: space-between;
  border-top: 1px solid var(--line); padding-top: 10px; margin-top: 2px;
}
.theme-meta-row code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--accent);
  background: var(--accent-soft); padding: 2px 6px; border-radius: 3px;
}
.theme-meta-row button.ghost {
  background: transparent; color: var(--fg-dim); border: 1px solid var(--line);
  font: inherit; font-size: 11px; padding: 3px 8px; border-radius: 3px; cursor: pointer;
}
.theme-meta-row button.ghost:hover { color: var(--fg); border-color: var(--fg-mute); }

/* ─── Topbar ───────────────────────────────────────────────────────── */
.topbar {
  position: fixed; top: 0; left: 0; right: 0; height: var(--topbar-h); z-index: 50;
  display: flex; align-items: center; gap: 10px; padding: 0 16px;
  background: rgba(7, 9, 13, 0.85); backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line); font-size: 12px;
}
.topbar .brand { font-weight: 700; letter-spacing: 0.04em; }
.topbar .spacer { flex: 1; }
.topbar .status { color: var(--fg-dim); }
.topbar .status.ok { color: var(--ok); }
.topbar .status.bad { color: var(--warn); }
.topbar .counter {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg-dim); font-variant-numeric: tabular-nums; padding: 0 6px;
}
.topbar button.ghost {
  background: transparent; color: var(--fg-dim); border: 1px solid var(--line);
  font: inherit; padding: 4px 10px; border-radius: 4px; cursor: pointer;
  transition: color 120ms, border-color 120ms;
}
.topbar button.ghost:hover { color: var(--fg); border-color: var(--fg-mute); }
.topbar button.ghost:disabled { opacity: 0.35; cursor: not-allowed; }
.topbar button.ghost.icon { padding: 4px 8px; min-width: 28px; }
.topbar button.ghost.active { color: var(--accent); border-color: var(--accent); }

/* ─── Layout ───────────────────────────────────────────────────────── */
.layout { position: relative; }
.canvas-wrapper {
  position: fixed;
  top: var(--topbar-h); left: 0; right: 0;
  height: calc(100vh - var(--topbar-h));
  z-index: 1; overflow: hidden;
  /* Layer-isolation properties (transform: translateZ, will-change,
     contain: paint) were tried here during the scroll-flicker
     investigation but caused glitchy cutout-mask artifacts on the
     foreground stage views — paint containment plus a
     GPU-promoted layer breaks compositor ordering of children's
     opacity transitions. Layer promotion stays only on .bg-canvas,
     which is enough to isolate bg from scroll. */
}
html, body { overflow-anchor: none; }
.article { overflow-anchor: none; }
.article {
  position: relative; z-index: 2;
  padding-top: var(--topbar-h); pointer-events: none;
}
.article .prose { pointer-events: auto; }

/* ─── Stage views ──────────────────────────────────────────────────── */
.stage { position: absolute; inset: 0; cursor: pointer; }
.stage-view {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: flex-start;
  /* Reserve full prose-w of right-side space so the metric stack
     ends exactly where the prose column starts (was prose-w - 1.5em,
     which let prose's feathered left edge sit over interactive
     widgets and block clicks). */
  padding-right: clamp(0px, var(--prose-w), 42em);
  /* No opacity transition: snapping scenes in/out instantly. The
     transition was the source of the grid-shape artifact that
     appeared over metric content during scene changes. While
     stage-view's opacity was animating between 0 and 1, the
     compositor sampled the perspective floor (bg-canvas's animated
     grid) into the stage-view's intermediate bitmap, and the
     grid pattern leaked into the metric content area for the
     duration of the transition. Removing the transition removes
     that compositor work entirely. */
  opacity: 0;
  pointer-events: none;
}
.stage-view[data-active] { opacity: 1; pointer-events: auto; }

/* Intro stage */
.bg-grid {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(88,166,255,0.07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(88,166,255,0.07) 1px, transparent 1px);
  background-size: 48px 48px;
  mask-image: radial-gradient(ellipse at center, #000 0%, transparent 75%);
  animation: drift 18s linear infinite;
}
/* DIAGNOSTIC: hide .bg-grid unconditionally (was previously hidden
   only on non-black themes). The grid-shape artifact reported as
   "appearing over metric content during scene transitions" matches
   the intro scene's .bg-grid being visible-through-transparency
   when both intro and the next scene's stage-view are
   simultaneously partially-opaque during the IntersectionObserver-
   driven cross-fade. .bg-grid is in the intro stage-view; the next
   scene's metric-stack has a transparent background, so the grid
   shows through during the transition window. Hiding it
   unconditionally for now to verify, then we'll reintroduce it on
   the black theme if it's actually wanted there. */
.bg-grid { display: none; }
@keyframes drift {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 48px 48px, 48px 48px; }
}
.intro-block {
  position: relative; z-index: 1; text-align: left;
  padding: 0 clamp(32px, 5vw, 80px); width: 100%;
}
.intro-eyebrow {
  font-size: 12px; letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--fg-dim); margin-bottom: 18px;
}
.intro-title {
  font-size: clamp(56px, 9vw, 168px); line-height: 0.95; font-weight: 700;
  letter-spacing: -0.04em;
  background: linear-gradient(180deg, var(--fg) 0%, var(--fg-dim) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}

/* ─── Metric stack — calculated, viewport-relative sizing ──────────── */
/* Wide-by-default. Inside a stage-view the right padding already
   reserves room for prose, so width:100% means "use everything left
   of the prose column." Backdrop is a horizontal gradient that
   fades at both edges so the card dissolves into the bg instead
   of meeting it with a hard rectangle. The right fade is wider
   (78%→100%) than the left (0%→8%) because the right edge meets
   the prose column's feathered left edge — letting the two
   feathers overlap produces a continuous transition from
   metric-card → bg → prose-card. */
.metric-stack {
  text-align: left;
  /* Asymmetric horizontal padding: less on the left (so the
     interactive widgets sit further left, closer to the viewport
     edge) and the existing larger value on the right (which holds
     the gradient fade and the prose column behind it).
     Vertical padding unchanged. */
  padding:
    clamp(20px, 2.5vh, 36px)
    clamp(40px, 5vw, 88px)
    clamp(20px, 2.5vh, 36px)
    clamp(20px, 2.5vw, 48px);
  width: 100%; max-width: none;
  display: flex; flex-direction: column;
  gap: clamp(10px, 1.4vh, 22px);
  background: linear-gradient(
    to right,
    rgba(var(--bg-rgb), 0)                                 0%,
    rgba(var(--bg-rgb), var(--content-backdrop, 0.30))     8%,
    rgba(var(--bg-rgb), var(--content-backdrop, 0.30))    78%,
    rgba(var(--bg-rgb), 0)                               100%
  );
}
.metric-stack-wide {
  /* Slightly less right padding than other stages so the table can
     stretch into the gradient zone of the prose column. */
  padding-right: clamp(24px, 3vw, 56px);
}
.metric-eyebrow {
  font-size: clamp(11px, 1vw, 14px);
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--fg-dim);
}
.metric-big {
  font-size: clamp(72px, 13vw, 240px);
  line-height: 0.95; font-weight: 700;
  letter-spacing: -0.04em; font-variant-numeric: tabular-nums;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.metric-sub { color: var(--fg-dim); font-size: clamp(13px, 1vw, 16px);
              line-height: 1.55; font-variant-numeric: tabular-nums; }
.metric-sub code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em; color: var(--accent);
  background: var(--accent-soft); padding: 1px 5px; border-radius: 3px;
}

.awaiting {
  color: var(--fg-mute); font-size: clamp(12px, 0.95vw, 14px);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 16px 0; font-style: italic;
}

/* Sparkline */
.sparkline {
  width: 100%; height: clamp(140px, 28vh, 360px); margin-top: 6px;
}
.sparkline path { fill: none; stroke: var(--accent); stroke-width: 1.5;
                  vector-effect: non-scaling-stroke; }
.sparkline #ingest-spark-fill { fill: var(--accent-soft); stroke: none; }

/* Per-host bars */
.bars { display: flex; flex-direction: column; gap: clamp(8px, 1.1vh, 14px); }
.bar-row {
  display: grid;
  grid-template-columns: minmax(140px, 18ch) 1fr minmax(72px, 10ch);
  gap: clamp(10px, 1vw, 18px); align-items: center;
  font-variant-numeric: tabular-nums;
}
.bar-host { color: var(--fg); font-size: clamp(13px, 1vw, 15px);
            font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.bar-track { height: clamp(24px, 3vh, 40px); background: var(--bg-elev);
             border: 1px solid var(--line); border-radius: 3px; overflow: hidden; }
.bar-fill { height: 100%; background: var(--accent);
            transition: width 600ms cubic-bezier(0.2, 0.8, 0.2, 1); }
.bar-count { color: var(--fg-dim); font-size: clamp(13px, 1vw, 15px); text-align: right;
             font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* Phase mix */
.phase-stack {
  display: flex; height: clamp(48px, 7vh, 96px);
  border-radius: 4px; overflow: hidden;
  background: var(--bg-elev); border: 1px solid var(--line);
}
.phase-seg { transition: flex-grow 600ms ease; flex-grow: 0; min-width: 0; }
.phase-seg.clean    { background: var(--phase-clean); }
.phase-seg.armed    { background: var(--phase-armed); }
.phase-seg.infecting { background: var(--phase-infecting); }
.phase-seg.infected_running { background: var(--phase-running); }
.phase-seg.dormant  { background: var(--phase-dormant); }
.phase-legend { display: flex; flex-wrap: wrap; gap: 14px;
                font-size: 12px; color: var(--fg-dim);
                font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.phase-legend > span { display: inline-flex; align-items: center; }
.phase-legend .swatch { display: inline-block; width: 10px; height: 10px;
                        border-radius: 2px; margin-right: 6px; }

/* ─── Code cards (stack scene) ─────────────────────────────────────── */
.code-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
  gap: clamp(12px, 1.4vw, 22px);
  align-items: start;
}
.code-card {
  background: var(--bg-elev2);
  border: 1px solid var(--line);
  border-radius: 4px;
  overflow: hidden;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: clamp(11px, 0.92vw, 14px);
  line-height: 1.6;
}
.code-card-header {
  padding: 7px 14px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--line);
  color: var(--fg-dim);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: lowercase;
}
.code-card pre.code {
  margin: 0;
  padding: 14px 18px;
  white-space: pre;
  overflow-x: auto;
  color: var(--fg);
  max-height: clamp(260px, 50vh, 560px);
}
/* Syntax-highlighting colors derive from the theme. Each token
   type sits at a fixed hue offset from --theme-h so the relative
   distinguishability between kw / str / fn / ty / num is preserved
   regardless of where the user moves the H slider — they all
   rotate together. Lightness fixed at 75% (readable on dark bg
   without straining), chroma fixed at 0.18 (saturated enough that
   the offsets read as distinct colors). Comments go through
   --fg-mute so they pick up the L-step grey transitions. */
.code-card .kw   { color: oklch(75% 0.18 calc(var(--theme-h, 250) + 0)); }
.code-card .str  { color: oklch(75% 0.18 calc(var(--theme-h, 250) + 220)); }
.code-card .com  { color: var(--fg-mute); font-style: italic; }
.code-card .fn   { color: oklch(75% 0.18 calc(var(--theme-h, 250) + 280)); }
.code-card .ty   { color: oklch(75% 0.18 calc(var(--theme-h, 250) + 40)); }
.code-card .num  { color: oklch(75% 0.18 calc(var(--theme-h, 250) + 200)); }

/* ─── Database explorer ────────────────────────────────────────────── */
.db-header {
  display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
}
.db-count { color: var(--fg-mute); font-size: 12px;
            font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
            font-variant-numeric: tabular-nums; }
.db-controls {
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
}
.db-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.db-tab {
  background: transparent; color: var(--fg-dim);
  border: 1px solid var(--line); border-radius: 16px;
  padding: 4px 12px; font: inherit; font-size: 12px; cursor: pointer;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  transition: color 120ms, border-color 120ms, background 120ms;
}
.db-tab:hover { color: var(--fg); border-color: var(--fg-mute); }
.db-tab.active { color: var(--accent); border-color: var(--accent);
                 background: var(--accent-soft); }
.db-search {
  flex: 1; min-width: 200px;
  background: var(--bg-elev); color: var(--fg);
  border: 1px solid var(--line); border-radius: 4px;
  padding: 6px 10px; font: inherit; font-size: 13px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.db-search:focus { outline: none; border-color: var(--accent); }
.db-table-wrap {
  flex: 1 1 auto; min-height: 0;
  max-height: clamp(280px, 56vh, 720px);
  overflow: auto;
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg-elev);
}
.db-table {
  width: 100%; border-collapse: collapse;
  font-size: clamp(12px, 0.92vw, 14px);
  font-variant-numeric: tabular-nums;
}
.db-table thead th {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-elev2); color: var(--fg-dim);
  text-align: left; padding: 8px 12px;
  font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase;
  font-weight: 500; border-bottom: 1px solid var(--line);
}
.db-table tbody tr {
  border-bottom: 1px solid var(--line-soft); cursor: pointer;
  transition: background 80ms;
}
.db-table tbody tr:hover { background: rgba(88, 166, 255, 0.06); }
.db-table tbody tr.selected { background: var(--accent-soft); }
.db-table td {
  padding: 7px 12px; color: var(--fg);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.db-table td.db-size { color: var(--fg-dim); text-align: right; }
.db-host { color: var(--fg); }
.db-id { color: var(--fg-dim); }
.db-detail {
  border: 1px solid var(--line); border-radius: 4px;
  background: var(--bg-elev);
  overflow: hidden;
  display: flex; flex-direction: column;
}
.db-detail[hidden] { display: none; }
.db-detail-meta {
  padding: 8px 14px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; color: var(--fg-dim);
  border-bottom: 1px solid var(--line-soft);
  display: flex; gap: 14px; flex-wrap: wrap;
}
.db-detail-meta .db-id { color: var(--fg); }
.db-detail-chart-wrap {
  background: var(--bg-elev2);
  width: 100%;
  position: relative;
}
.db-detail-chart {
  display: block;
  width: 100%;
  height: clamp(220px, 32vh, 420px);
}
.db-detail-chart .axis { stroke: var(--line); stroke-width: 1; }
.db-detail-chart .tick {
  fill: var(--fg-mute); font-size: 10px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.db-detail-chart .metric-line {
  fill: none; stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}
.db-detail-chart .phase-band { opacity: 0.18; }
.db-detail-chart .phase-band.clean    { fill: var(--phase-clean); }
.db-detail-chart .phase-band.armed    { fill: var(--phase-armed); }
.db-detail-chart .phase-band.infecting { fill: var(--phase-infecting); }
.db-detail-chart .phase-band.infected_running { fill: var(--phase-running); }
.db-detail-chart .phase-band.dormant  { fill: var(--phase-dormant); }
.db-detail-chart .placeholder {
  fill: var(--fg-mute); font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.db-detail-legend {
  display: flex; flex-wrap: wrap; gap: 14px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px; color: var(--fg-dim);
  padding: 8px 14px;
  border-top: 1px solid var(--line-soft);
}
.db-detail-legend > span { display: inline-flex; align-items: center; }
.db-detail-legend .swatch {
  display: inline-block; width: 10px; height: 10px;
  border-radius: 2px; margin-right: 6px; vertical-align: middle;
}

/* ─── Attack envelope thumbnails ───────────────────────────────────── */
.profile-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(12px, 1.4vw, 22px);
}
.profile-card {
  border: 1px solid var(--line); border-radius: 4px;
  padding: clamp(12px, 1.2vw, 18px);
  background: rgba(13, 17, 23, 0.6);
}
.profile-name {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: clamp(13px, 1vw, 15px); color: var(--fg); margin-bottom: 4px;
}
.profile-shape {
  font-size: clamp(11px, 0.85vw, 13px); color: var(--fg-dim);
  margin-bottom: 8px; line-height: 1.4;
}
.profile-card svg {
  display: block; width: 100%;
  height: clamp(56px, 9vh, 120px);
}
.profile-card svg path { fill: none; stroke: var(--accent); stroke-width: 1.4;
                         vector-effect: non-scaling-stroke; }

/* ─── Chunking timeline ────────────────────────────────────────────── */
.chunk-rule, .chunk-row, .chunk-axis { display: flex; gap: 4px; }
.chunk-row { height: clamp(56px, 9vh, 120px); }
.chunk-cell {
  flex: 1; border-radius: 3px;
  display: flex; align-items: center; justify-content: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: clamp(11px, 0.95vw, 14px); color: var(--fg);
}
.chunk-cell.clean { background: var(--phase-clean); }
.chunk-cell.armed { background: var(--phase-armed); }
.chunk-cell.infecting { background: var(--phase-infecting); }
.chunk-cell.infected_running { background: var(--phase-running); }
.chunk-cell.dormant { background: var(--phase-dormant); }
.chunk-rule { height: 8px; background: var(--bg-elev);
              border: 1px solid var(--line); border-radius: 2px; padding: 1px; }
.chunk-rule .tick { flex: 1; border-right: 1px solid var(--line); }
.chunk-rule .tick:last-child { border-right: none; }
.chunk-axis { height: 16px; font-size: 10px; color: var(--fg-mute);
              font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.chunk-axis span { flex: 1; text-align: center; }

/* ─── Model bars ───────────────────────────────────────────────────── */
.model-bars { display: flex; flex-direction: column; gap: clamp(10px, 1.5vh, 18px); }
.model-row {
  display: grid; grid-template-columns: minmax(80px, 12ch) 1fr minmax(64px, 9ch);
  gap: clamp(10px, 1vw, 18px); align-items: center; font-variant-numeric: tabular-nums;
}
.model-name { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
              font-size: clamp(13px, 1vw, 15px); color: var(--fg); }
.model-track { height: clamp(28px, 4vh, 48px); background: var(--bg-elev);
               border: 1px solid var(--line); border-radius: 3px; overflow: hidden; }
.model-fill {
  height: 100%;
  transition: width 600ms cubic-bezier(0.2, 0.8, 0.2, 1);
  /* Neutral fallback so any model name the producer emits at least
     paints a visible bar, even one we didn't anticipate. Specific
     gradients below override per canonical model name. */
  background: linear-gradient(90deg, var(--fg-mute), var(--fg-dim));
}
/* !important on every gradient because something — most likely a
   stale browser cache — has been silently overriding mlp / cnn /
   knn_semi mid-talk. Belt-and-suspenders before a presentation. */
.model-fill.lstm            { background: linear-gradient(90deg, #58a6ff, #1f6feb) !important; }
.model-fill.gru             { background: linear-gradient(90deg, #db61a2, #a8327f) !important; }
.model-fill.rnn             { background: linear-gradient(90deg, #d29922, #8a6a17) !important; }
.model-fill.bert            { background: linear-gradient(90deg, #f85149, #b22e2a) !important; }
.model-fill.knn             { background: linear-gradient(90deg, #3fb950, #1a7f37) !important; }
.model-fill.gbt             { background: linear-gradient(90deg, #ff8c42, #c2410c) !important; }
.model-fill.mlp             { background: linear-gradient(90deg, #a371f7, #6e40c9) !important; }
.model-fill.cnn             { background: linear-gradient(90deg, #34d399, #047857) !important; }
.model-fill.knn_semi        { background: linear-gradient(90deg, #2dd4bf, #115e59) !important; }
.model-fill.transformer     { background: linear-gradient(90deg, #fde047, #ca8a04) !important; }
.model-fill.transformer_ssl { background: linear-gradient(90deg, #c0a4ff, #8c6bff) !important; }
.model-acc { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
             font-size: clamp(13px, 1vw, 15px); color: var(--fg-dim); text-align: right; }

/* ─── KNN 3-D scatter (canvas) ─────────────────────────────────────── */
.scatter3d-controls {
  display: flex; flex-wrap: wrap; gap: 12px;
  align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.scatter3d-modes { display: flex; flex-wrap: wrap; gap: 6px; }
.scatter3d-mode, .scatter3d-reset {
  background: transparent; color: var(--fg-dim);
  border: 1px solid var(--line); border-radius: 16px;
  padding: 4px 12px; font-size: 12px; cursor: pointer;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  transition: color 120ms, border-color 120ms, background 120ms;
}
.scatter3d-mode:hover, .scatter3d-reset:hover {
  color: var(--fg); border-color: var(--fg-mute);
}
.scatter3d-mode.active {
  color: var(--accent); border-color: var(--accent);
  background: var(--accent-soft, rgba(80, 140, 220, 0.12));
}
.scatter3d-wrap {
  position: relative;
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line); border-radius: 4px;
  width: 100%;
  height: clamp(320px, 56vh, 640px);
  overflow: hidden;
  cursor: grab;
  touch-action: none;
}
.scatter3d-wrap:active { cursor: grabbing; }
.scatter3d {
  display: block;
  width: 100%; height: 100%;
}

/* ─── Motivation cards (scene: motivation) ─────────────────────────── */
.motivation-stack { gap: clamp(12px, 1.8vh, 22px); }
.motivation-cards {
  display: flex; flex-direction: column;
  gap: clamp(10px, 1.4vh, 18px);
}
.motivation-card {
  display: grid;
  grid-template-columns: 6px 1fr;
  gap: clamp(12px, 1.4vw, 18px);
  padding: clamp(12px, 1.6vh, 20px) clamp(14px, 1.6vw, 22px);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
  align-items: stretch;
}
.motivation-card-marker {
  border-radius: 2px;
}
.motivation-card-marker.mc-trust    { background: var(--accent); }
.motivation-card-marker.mc-contain  { background: var(--phase-armed); }
.motivation-card-marker.mc-recover  { background: var(--phase-clean); }
.motivation-card-body {
  display: flex; flex-direction: column;
  gap: clamp(4px, 0.6vh, 8px);
  min-width: 0;
}
.motivation-card-title {
  font: 600 clamp(14px, 1.2vw, 18px)
    ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg);
  letter-spacing: 0.02em;
}
.motivation-card-text {
  font-size: clamp(13px, 1vw, 15px);
  line-height: 1.5;
  color: var(--fg-dim);
}

/* ─── Problem statement (scene: problem-statement) ─────────────────── */
.problem-claim {
  padding: clamp(16px, 2vh, 28px) clamp(18px, 2vw, 28px);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent);
  border-radius: 4px;
}
.problem-claim-text {
  font-size: clamp(16px, 1.5vw, 22px);
  line-height: 1.45;
  color: var(--fg);
  font-weight: 500;
}
.problem-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(10px, 1.4vw, 18px);
}
.problem-stat {
  padding: clamp(14px, 1.8vh, 22px);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
  display: flex; flex-direction: column; gap: 4px;
  align-items: flex-start;
}
.problem-stat-num {
  font: 700 clamp(28px, 3.4vw, 44px)
    ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--accent);
  line-height: 1;
}
.problem-stat-lbl {
  font-size: clamp(12px, 0.95vw, 14px);
  color: var(--fg-dim);
  line-height: 1.35;
}
.problem-task {
  padding: 12px 16px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: clamp(13px, 1vw, 15px);
  color: var(--fg-dim);
  line-height: 1.5;
}
.problem-task-label { color: var(--fg-mute); margin-right: 6px; }
.problem-task-value { color: var(--fg); font-weight: 600; }
.problem-task-detail { color: var(--fg-dim); }

/* ─── Research questions (scene: research-questions) ───────────────── */
.research-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(12px, 1.6vw, 22px);
}
.research-col {
  padding: clamp(14px, 1.8vh, 22px);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
}
.research-col-title {
  font: 600 clamp(13px, 1.05vw, 15px)
    ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--accent);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 12px;
}
.research-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 10px;
  font-size: clamp(13px, 1vw, 15px);
  line-height: 1.45;
  color: var(--fg-dim);
}
.research-list li::before {
  content: '·'; color: var(--accent); margin-right: 8px;
}
.research-list strong { color: var(--fg); }

/* ─── Solution overview (scene: solution-overview) ─────────────────── */
.pipeline-svg {
  width: 100%;
  height: clamp(360px, 60vh, 640px);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 12px;
  box-sizing: border-box;
}
.pipeline-stage rect {
  fill: var(--bg);
  stroke: var(--accent);
  stroke-width: 1.5;
}
.pipeline-stage-models rect {
  fill: var(--accent-soft, rgba(80, 140, 220, 0.08));
  stroke-width: 2;
}
.pipeline-stage-final rect {
  stroke: var(--phase-clean);
}
.pipeline-stage text {
  fill: var(--fg);
  font: 600 14px ui-monospace, SFMono-Regular, Menlo, monospace;
}
.pipeline-stage-title {
  font-size: 16px !important;
}
.pipeline-detail {
  fill: var(--fg-dim) !important;
  font-weight: 400 !important;
  font-size: 11px !important;
}
.pipeline-detail-mini {
  fill: var(--fg-mute) !important;
  font-weight: 400 !important;
  font-size: 10px !important;
}
.pipeline-arrow path {
  stroke: var(--fg-mute);
  stroke-width: 1.5;
  stroke-linecap: round;
  marker-end: url(#pipe-arrow);
}

/* ─── Evaluation setup (scene: evaluation-setup) ───────────────────── */
.eval-blocks {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(10px, 1.4vw, 18px);
}
.eval-block {
  padding: clamp(12px, 1.6vh, 18px);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
  display: flex; flex-direction: column; gap: 8px;
}
.eval-block-title {
  font: 600 clamp(12px, 0.95vw, 14px)
    ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--accent);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.eval-block-body {
  display: flex; flex-direction: column; gap: 6px;
  font-size: clamp(13px, 1vw, 15px);
  color: var(--fg-dim);
  line-height: 1.45;
}
.eval-block-body strong { color: var(--fg); }
.eval-detail {
  margin-top: 4px;
  color: var(--fg-mute);
  font-size: clamp(12px, 0.9vw, 13px);
  font-style: italic;
}

/* ─── Conclusion + future (scene: conclusion-future) ───────────────── */
.conclusion-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(12px, 1.6vw, 22px);
}
.conclusion-col {
  padding: clamp(14px, 1.8vh, 22px);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
}
.conclusion-col-title {
  font: 600 clamp(13px, 1.05vw, 15px)
    ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--accent);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 12px;
}
.conclusion-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 10px;
  font-size: clamp(13px, 1vw, 15px);
  line-height: 1.45;
  color: var(--fg-dim);
}
.conclusion-list li::before {
  content: '·'; color: var(--accent); margin-right: 8px;
}
.conclusion-list strong { color: var(--fg); }

/* ─── Limitations card uses the motivation-card pattern with an
   armed-phase marker for the "warning" feel.                      ─── */
.motivation-card-marker.mc-armed { background: var(--phase-armed); }

/* ─── Live detections (scene: live) ────────────────────────────────── */
.live-stack { gap: clamp(10px, 1.6vh, 20px); }

.live-stats {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
  padding: 8px 14px;
  font: 12px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg-dim);
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
}
.live-stats-eye { color: var(--accent); font-weight: 600; }
.live-stats-dot::before { content: '·'; margin-right: 12px; opacity: 0.5; }

.live-lanes {
  display: flex; flex-direction: column;
  gap: clamp(4px, 0.8vh, 8px);
  padding: 12px;
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
  min-height: 220px;
}
.live-lanes:empty::before {
  content: 'no hosts reporting yet';
  align-self: center;
  margin: auto;
  color: var(--fg-mute);
  font: 13px ui-monospace, SFMono-Regular, Menlo, monospace;
}
.live-lane {
  display: grid;
  grid-template-columns: minmax(120px, 16ch) 1fr;
  gap: 12px; align-items: center;
}
.live-lane-host {
  font: 13px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.live-lane-cells {
  display: flex; gap: 1px;
  height: clamp(24px, 4vh, 36px);
  overflow: hidden;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 2px;
}
.live-cell {
  flex: 1 1 0;
  position: relative;
  min-width: 4px;
}
.live-cell.clean            { background: var(--phase-clean); }
.live-cell.armed            { background: var(--phase-armed); }
.live-cell.infecting        { background: var(--phase-infecting); }
.live-cell.infected_running { background: var(--phase-running); }
.live-cell.dormant          { background: var(--phase-dormant); }
.live-cell.miss::after {
  content: ''; position: absolute; inset: 0;
  background: repeating-linear-gradient(
    -45deg, transparent 0 3px, rgba(0, 0, 0, 0.55) 3px 5px
  );
}

.live-latest {
  padding: 14px 16px;
  background: var(--bg-elev, rgba(255, 255, 255, 0.03));
  border: 1px solid var(--line);
  border-radius: 4px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 16px; align-items: center;
}
.live-latest-empty {
  grid-column: 1 / -1;
  text-align: center;
  color: var(--fg-mute);
  font: 13px ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 12px 0;
}
.live-phase-block {
  width: clamp(80px, 9vw, 110px);
  aspect-ratio: 1;
  border-radius: 4px;
  display: grid; place-items: center;
  font: clamp(12px, 1vw, 14px) ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700;
  color: rgba(0, 0, 0, 0.85);
  text-align: center; padding: 8px;
  line-height: 1.15;
}
.live-phase-block.clean            { background: var(--phase-clean); }
.live-phase-block.armed            { background: var(--phase-armed); }
.live-phase-block.infecting        { background: var(--phase-infecting); }
.live-phase-block.infected_running { background: var(--phase-running); }
.live-phase-block.dormant          { background: var(--phase-dormant); }

.live-meta {
  display: flex; flex-direction: column; gap: 4px;
  font: 13px ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg-dim);
  min-width: 0;
}
.live-meta-host  { color: var(--fg); font-weight: 600; }
.live-meta-line code { color: var(--fg); }
.live-conf {
  text-align: right;
  font: clamp(20px, 2vw, 28px) ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fg);
  white-space: nowrap;
}
.live-conf-label {
  display: block;
  font-size: 11px; color: var(--fg-mute);
  font-weight: normal;
  margin-bottom: 2px;
}

/* ─── Scatter plots (KNN, perf) ────────────────────────────────────── */
.scatter {
  width: 100%;
  height: clamp(320px, 60vh, 640px);
}
.scatter .axis { stroke: var(--line); stroke-width: 1; }
.scatter .axis-label { fill: var(--fg-mute); font-size: 12px;
                       font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.scatter .point { transition: r 200ms; }
.scatter .point.clean    { fill: var(--phase-clean); }
.scatter .point.armed    { fill: var(--phase-armed); }
.scatter .point.infecting { fill: var(--phase-infecting); }
.scatter .point.infected_running { fill: var(--phase-running); }
.scatter .point.dormant  { fill: var(--phase-dormant); }
.scatter .perf-point { fill: var(--accent); stroke: #1f6feb; stroke-width: 1.5; }
.scatter .perf-label { fill: var(--fg); font-size: 13px;
                       font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* ─── Floating advance button ──────────────────────────────────────── */
.fab {
  position: absolute; right: 24px; bottom: 24px; z-index: 5;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(13, 17, 23, 0.85); color: var(--fg-dim);
  border: 1px solid var(--line); font-size: 16px; cursor: pointer;
  backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  transition: color 150ms, border-color 150ms, transform 150ms;
}
.fab:hover { color: var(--accent); border-color: var(--accent); transform: translateY(-1px); }
.fab:disabled { opacity: 0.25; cursor: not-allowed; transform: none; }

/* ─── Article (prose, overlaid right) ──────────────────────────────── */
.scene {
  display: flex; align-items: center; justify-content: flex-end;
  min-height: 100vh;
  /* 0.5rem right padding instead of 2rem — shifts the prose card
     ~1.5rem further right so it sits cleanly past the interactive
     widgets in the metric stack. Bottom/top still 4rem; left 2rem
     stays as scroll-margin for the prose column. */
  padding: 4rem 0.5rem 4rem 2rem;
}

/* ─── Scene virtualization (mount window: active ± 1) ─────────────────
   Only three scenes are "mounted" at a time — the active one, plus its
   immediate neighbours. Far scenes get content-visibility: hidden which
   skips paint and rasterization while preserving layout (so the
   scrollbar position and IntersectionObserver geometry stay correct).
   contain-intrinsic-size declares the placeholder height so the
   document doesn't reflow as scenes mount/unmount on scroll.

   For the absolutely-positioned stage views (which all overlap in the
   same box), far ones can use display: none outright — they don't
   contribute to layout flow either way. */
.scene[data-stage]:not([data-mounted]) {
  content-visibility: hidden;
  contain-intrinsic-size: 1px 100vh;
}
.stage-view[data-view]:not([data-mounted]) {
  display: none;
}
.scene .prose {
  width: var(--prose-w); max-width: calc(100vw - 4rem);
  padding: 2.25rem 2.5rem 2.25rem 5rem;
  font-size: 17px; line-height: 1.65; color: var(--fg);
  /* Two background layers: the existing left-feathering gradient on
     top, plus a uniform backdrop underneath driven by
     --content-backdrop. When the backdrop is 0, the gradient's
     transparent left edge reveals bg behind (matrix-explorable
     look). When the backdrop is non-zero, the transparent edge
     reveals a partly-opaque dark layer instead, so the prose stays
     legible over busy themes. */
  background:
    linear-gradient(
      to left,
      rgba(var(--bg-rgb), 0.96) 0%,
      rgba(var(--bg-rgb), 0.95) 70%,
      rgba(var(--bg-rgb), 0.0)  100%
    ),
    rgba(var(--bg-rgb), var(--content-backdrop, 0));
  opacity: 0; transform: translateY(20px);
  transition: opacity var(--scene-fade-ms) ease,
              transform var(--scene-fade-ms) ease;
}
.scene[data-active] .prose { opacity: 1; transform: translateY(0); }
.scene .prose h2 { margin: 0 0 14px; font-size: 24px; font-weight: 600;
                   letter-spacing: -0.01em; }
.scene .prose .lede { font-size: 24px; line-height: 1.4; font-weight: 500;
                      margin: 0 0 20px; }
.scene .prose p { margin: 0 0 14px; }
.scene .prose strong { color: var(--fg); font-weight: 600; }
.scene .prose code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em; color: var(--accent);
  background: var(--accent-soft); padding: 1px 5px; border-radius: 3px;
}
.scene .prose .hint {
  color: var(--fg-mute); font-size: 12px;
  letter-spacing: 0.16em; text-transform: uppercase; margin-top: 28px;
}

.scene-end-spacer { height: 30vh; }

@media (max-width: 880px) {
  :root { --prose-w: 92vw; }
  .stage-view { padding-right: 0; padding-bottom: 50vh; }
  .intro-block, .metric-stack { padding: 0 24px; }
  .bar-row { grid-template-columns: 110px 1fr 60px; }
  .model-row { grid-template-columns: 80px 1fr 56px; }
  .profile-grid { grid-template-columns: 1fr; }
  .scene { padding: 2rem 1rem; min-height: 80vh; justify-content: center; }
  .scene .prose { padding: 1.5rem; }
  .topbar .counter { display: none; }
  .db-table-wrap { max-height: 40vh; }
}
