/* ============================================================
   MARLOWE — base.css
   Tokens, reset, typography, chrome (nav / cursor / overlays)
   ============================================================ */

/* ---- Registered custom properties (@property) ---- */
@property --n {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
@property --hue-shift {
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}
@property --sheen {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}
@property --focus {
  /* 0 = card at the rail's edge, 1 = card snapped center stage */
  syntax: "<number>";
  initial-value: 0;
  inherits: true;
}
@property --on {
  /* 0 = nav link idle, 1 = its section owns the viewport */
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}
@property --lift {
  /* 0..1 pointer proximity for the hero letters */
  syntax: "<number>";
  initial-value: 0;
  inherits: false;
}

:root {
  /* Accent hue drifts as you scroll (see effects.css) */
  --hue-base: 295;
  --hue: calc(var(--hue-base) + var(--hue-shift));

  --bg: oklch(13% 0.025 285);
  --bg-deep: oklch(9% 0.02 285);
  --ink: oklch(93% 0.015 285);
  --ink-dim: oklch(72% 0.02 285);
  --ink-faint: oklch(55% 0.02 285);

  --accent: oklch(75% 0.19 var(--hue));
  --accent-2: oklch(78% 0.16 calc(var(--hue) - 100));
  --accent-hot: oklch(70% 0.24 calc(var(--hue) + 40));

  /* Relative color syntax: derive surfaces from the accent */
  --glass: oklch(from var(--accent) 17% calc(c * 0.25) h / 0.52);
  --glass-edge: oklch(from var(--accent) 80% calc(c * 0.5) h / 0.16);
  --glow: oklch(from var(--accent) l c h / 0.35);

  --font-display: "Space Grotesk", system-ui, sans-serif;
  --font-serif: "Instrument Serif", Georgia, serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;

  /* Springy easing via linear() */
  --ease-spring: linear(
    0, 0.009, 0.035 2.1%, 0.141 4.4%, 0.723 12.9%, 0.938 16.7%, 1.017,
    1.077, 1.121, 1.149 24.3%, 1.159, 1.163, 1.161, 1.154 29.9%, 1.129 32.8%,
    1.051 39.6%, 1.017 43.1%, 0.991, 0.977 51%, 0.974 53.8%, 0.975 57.1%,
    0.997 69.8%, 1.003 76.9%, 1
  );
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);

  interpolate-size: allow-keywords;
  accent-color: var(--accent);
  scrollbar-color: oklch(from var(--accent) 50% c h / 0.5) transparent;
}

/* ---- Reset ---- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: 5rem;
}

body {
  background: var(--bg-deep);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 380;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

::selection {
  background: oklch(from var(--accent) l c h / 0.85);
  color: oklch(12% 0.02 285);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

a { color: inherit; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
code { font-family: var(--font-mono); font-size: 0.9em; color: var(--accent-2); }

.skip-link {
  position: fixed;
  top: -4rem;
  left: 1rem;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--accent);
  color: black;
  border-radius: 0.5rem;
  font-weight: 600;
  text-decoration: none;
  transition: top 0.25s var(--ease-out);
}
.skip-link:focus-visible { top: 1rem; }

/* ---- Ambient layers ---- */
#gl {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -2;
  background: var(--bg-deep);
}

/* No-WebGL fallback: an animated CSS nebula so the page still breathes */
.no-gl #gl { display: none; }
.no-gl body, body.no-gl {
  background:
    radial-gradient(60% 50% at 20% 15%, oklch(35% 0.12 310 / 0.5), transparent 70%),
    radial-gradient(50% 45% at 85% 30%, oklch(35% 0.12 200 / 0.4), transparent 70%),
    radial-gradient(70% 60% at 50% 90%, oklch(30% 0.1 350 / 0.4), transparent 70%),
    var(--bg-deep);
}

.vignette {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 90% at 50% 40%, transparent 45%, oklch(6% 0.02 285 / 0.85) 100%);
}

.grain {
  position: fixed;
  inset: -50%;
  z-index: 40;
  pointer-events: none;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain-shift 0.9s steps(4) infinite;
}
@keyframes grain-shift {
  0% { translate: 0 0; }
  25% { translate: -2% 3%; }
  50% { translate: 3% -2%; }
  75% { translate: -3% -3%; }
  100% { translate: 2% 2%; }
}

/* ---- Custom cursor ---- */
#cursor {
  position: fixed;
  z-index: 90;
  inset: 0 auto auto 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s;
}
body.has-cursor #cursor { opacity: 1; }
#cursor i {
  position: absolute;
  width: 36px;
  height: 36px;
  translate: -50% -50%;
  border: 1.5px solid oklch(from var(--accent) l c h / 0.8);
  border-radius: 50%;
  box-shadow: 0 0 24px var(--glow), inset 0 0 12px oklch(from var(--accent) l c h / 0.15);
  transition:
    width 0.3s var(--ease-spring),
    height 0.3s var(--ease-spring),
    background-color 0.3s;
}
body.cursor-hot #cursor i {
  width: 64px;
  height: 64px;
  background: oklch(from var(--accent) l c h / 0.12);
}
/* mid-drag on the exploit rail: the ring closes to a grip */
body.cursor-drag #cursor i {
  width: 22px;
  height: 22px;
  background: oklch(from var(--accent) l c h / 0.3);
}
@media (pointer: fine) {
  body.has-cursor, body.has-cursor a, body.has-cursor button { cursor: none; }
}

/* ---- Nav ---- */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  /* scroll-state container: style children when stuck (Chrome 133+) */
  container-type: scroll-state;
  /* sticky needs a bleed to detect stuck */
  margin-top: -1px;
  padding-top: 1px;
}
.nav-inner {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2.5rem);
  padding: 0.9rem clamp(1rem, 4vw, 3rem);
  transition: background-color 0.4s, backdrop-filter 0.4s, box-shadow 0.4s;
}
@container scroll-state(stuck: top) {
  .nav-inner {
    background: oklch(from var(--bg) l c h / 0.55);
    backdrop-filter: blur(18px) saturate(1.5);
    box-shadow: 0 1px 0 oklch(from var(--accent) 70% c h / 0.12);
  }
}

.brand {
  font-weight: 700;
  font-size: 1.5rem;
  text-decoration: none;
  letter-spacing: -0.02em;
}
.brand span { color: var(--accent); }

.nav .nav-rail {
  display: flex;
  gap: clamp(0.75rem, 2vw, 1.6rem);
  margin-inline: auto;
}
.nav-rail a {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  /* --on is scrubbed by that section's view timeline (see effects.css) */
  color: color-mix(in oklab, var(--ink-dim), var(--accent) calc(var(--on) * 100%));
  transition: color 0.25s;
  position: relative;
}
.nav-rail a:hover { color: var(--ink); }
.nav-rail a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -4px;
  height: 1px;
  width: 100%;
  background: var(--accent);
  scale: var(--on) 1;
  transform-origin: left;
  transition: scale 0.3s var(--ease-out);
  box-shadow: 0 0 8px oklch(from var(--accent) l c h / calc(var(--on) * 0.7));
}
.nav-rail a:hover::after { scale: 1 1; }

/* ---- Compact section index (narrow viewports) ---- */
.menu-btn {
  display: none;
  align-items: center;
  gap: 0.5rem;
  margin-inline-start: auto;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--glass-edge);
  border-radius: 999px;
  corner-shape: squircle;
  background: var(--glass);
  backdrop-filter: blur(8px);
  anchor-name: --menu;
  transition: border-color 0.3s, box-shadow 0.3s;
}
.menu-btn:hover { border-color: oklch(from var(--accent) l c h / 0.5); }
.menu-label {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.2em;
  color: var(--ink-dim);
}
.menu-ico {
  display: grid;
  gap: 3px;
  width: 13px;
}
.menu-ico i {
  height: 1.5px;
  background: var(--accent);
  border-radius: 2px;
  transition: translate 0.35s var(--ease-spring), opacity 0.25s;
}
.menu-btn[aria-expanded="true"] .menu-ico i:nth-child(1) { translate: 0 4.5px; }
.menu-btn[aria-expanded="true"] .menu-ico i:nth-child(2) { opacity: 0; }
.menu-btn[aria-expanded="true"] .menu-ico i:nth-child(3) { translate: 0 -4.5px; }

.menu-sheet {
  /* fallback placement for browsers without anchor positioning */
  position: fixed;
  inset: auto 1rem auto auto;
  top: 4.4rem;
  margin: 0;
  width: min(17rem, calc(100vw - 2rem));
  padding: 1.1rem 1.2rem 1.3rem;
  border: 1px solid oklch(from var(--accent) 70% c h / 0.3);
  border-radius: 1.2rem;
  corner-shape: squircle;
  background: oklch(from var(--bg) calc(l - 0.02) c h / 0.94);
  backdrop-filter: blur(20px) saturate(1.4);
  box-shadow: 0 30px 70px -25px oklch(4% 0.02 285 / 0.9);
  color: var(--ink);
  overflow: visible;
  opacity: 1;
  scale: 1;
  transition:
    opacity 0.3s var(--ease-out),
    scale 0.35s var(--ease-spring),
    display 0.35s allow-discrete,
    overlay 0.35s allow-discrete;
  transform-origin: top right;
}
@supports (anchor-name: --a) {
  .menu-sheet {
    position-anchor: --menu;
    position-area: block-end span-inline-start;
    position-try-fallbacks: flip-block, flip-inline;
    inset: auto;
    margin-block-start: 0.6rem;
  }
}
.menu-sheet:not(:popover-open) { opacity: 0; scale: 0.92; }
.menu-sheet:popover-open {
  @starting-style { opacity: 0; scale: 0.92; }
}
.menu-sheet::backdrop {
  background: oklch(6% 0.02 285 / 0.5);
  backdrop-filter: blur(2px);
  opacity: 1;
  transition: opacity 0.3s, display 0.3s allow-discrete, overlay 0.3s allow-discrete;
}
.menu-sheet:not(:popover-open)::backdrop { opacity: 0; }
.menu-sheet:popover-open::backdrop {
  @starting-style { opacity: 0; }
}
.menu-title {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.3em;
  color: var(--accent);
  margin-bottom: 0.9rem;
}
.menu-sheet .nav-rail {
  display: grid;
  gap: 0.15rem;
}
.menu-sheet .nav-rail a {
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.6rem 0.7rem;
  border-radius: 0.7rem;
  corner-shape: squircle;
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  background: oklch(from var(--accent) 50% c h / calc(var(--on) * 0.14));
  transition: background-color 0.3s, color 0.25s;
}
.menu-sheet .nav-rail a::before {
  content: attr(data-i);
  font-size: 0.62rem;
  color: oklch(from var(--accent) l c h / calc(0.4 + var(--on) * 0.6));
}
/* the sliding underline belongs to the header rail, not the list */
.menu-sheet .nav-rail a::after { display: none; }
.menu-sheet .nav-rail a:hover { background: oklch(from var(--accent) 50% c h / 0.18); }

@media (width < 860px) {
  .nav .nav-rail { display: none; }
  .menu-btn { display: flex; }
}

/* ---- Visually hidden, still announced ---- */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Audio toggle */
.audio-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--glass-edge);
  border-radius: 999px;
  corner-shape: squircle;
  background: var(--glass);
  backdrop-filter: blur(8px);
  transition: border-color 0.3s, box-shadow 0.3s;
}
.audio-toggle:hover {
  border-color: oklch(from var(--accent) l c h / 0.5);
  box-shadow: 0 0 20px var(--glow);
}
.audio-label {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.2em;
  color: var(--ink-dim);
}
.bars {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 12px;
}
.bars i {
  width: 2.5px;
  height: 30%;
  background: var(--accent);
  border-radius: 2px;
  transition: height 0.3s;
}
.audio-toggle[aria-pressed="true"] .bars i {
  animation: eq 0.9s ease-in-out infinite alternate;
  animation-delay: calc(var(--b, 0) * -0.22s);
}
.bars i:nth-child(1) { --b: 0; }
.bars i:nth-child(2) { --b: 1; height: 70%; }
.bars i:nth-child(3) { --b: 2; height: 50%; }
.bars i:nth-child(4) { --b: 3; height: 90%; }
@keyframes eq {
  from { height: 20%; }
  to { height: 100%; }
}
.audio-toggle[aria-pressed="true"] .audio-label { color: var(--accent); }

/* Scroll progress bar (scrubbed by scroll — see effects.css) */
.progress {
  height: 2px;
  background: linear-gradient(90deg, var(--accent-2), var(--accent), var(--accent-hot));
  transform-origin: left;
  scale: 0 1;
  box-shadow: 0 0 12px var(--glow);
}

/* ---- Shared section furniture ---- */
main { display: block; }

section {
  position: relative;
  padding: clamp(6rem, 14vh, 10rem) clamp(1.25rem, 5vw, 4rem);
  max-width: 1200px;
  margin-inline: auto;
}

.section-head { margin-bottom: clamp(2.5rem, 6vh, 4.5rem); max-width: 46rem; }

.kicker {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.28em;
  color: var(--accent);
  margin-bottom: 1rem;
}
.kicker::before {
  content: "▸ ";
  color: var(--accent-hot);
}

h2 {
  font-size: clamp(2.2rem, 6vw, 4rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.02;
  text-wrap: balance;
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
  margin-bottom: 1.2rem;
}
h2 em, .hero-sub em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: 0;
  background: linear-gradient(100deg, var(--accent-2), var(--accent) 55%, var(--accent-hot));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.lede {
  color: var(--ink-dim);
  font-size: clamp(1rem, 1.4vw, 1.15rem);
  text-wrap: pretty;
  max-width: 40rem;
}
/* ============================================================
   MARLOWE — sections.css
   Hero, origins stage, exploits carousel, numbers, capabilities,
   dossier, signals, colophon, footer
   ============================================================ */

/* ---- HERO ---- */
.hero {
  min-height: 100svh;
  max-width: none;
  display: grid;
  place-content: center;
  justify-items: center;
  text-align: center;
  padding-top: 0;
  padding-bottom: 0;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: clamp(0.6rem, 1.4vw, 0.75rem);
  letter-spacing: 0.32em;
  color: var(--ink);
  text-shadow: 0 1px 14px oklch(10% 0.02 285 / 0.95);
  margin-bottom: clamp(1rem, 3vh, 2rem);
  animation: fade-up 0.9s var(--ease-out) 0.05s both;
}
.blink {
  color: oklch(70% 0.22 25);
  animation: blink 1.6s steps(2) infinite;
  margin-right: 0.4em;
}
@keyframes blink { 50% { opacity: 0.15; } }

.hero-title {
  font-size: clamp(3.4rem, 16.5vw, 13.5rem);
  font-weight: 640;
  letter-spacing: 0.02em;
  line-height: 0.95;
  display: flex;
  user-select: none;
}
.hero-title span {
  display: inline-block;
  background: linear-gradient(
    175deg,
    oklch(97% 0.02 var(--hue)) 25%,
    oklch(80% 0.14 var(--hue)) 55%,
    oklch(62% 0.19 calc(var(--hue) - 80)) 90%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* --mx/--my/--lift are written per-frame from the pointer (main.js).
     transform is used rather than `translate` so the intro keyframes,
     which animate `translate`, stay completely independent of it. */
  transform:
    translate3d(var(--mx, 0px), var(--my, 0px), 0)
    scale(calc(1 + var(--lift, 0) * 0.05));
  filter: drop-shadow(0 6px 40px var(--glow))
          brightness(calc(1 + var(--lift, 0) * 0.18));
  animation: letter-in 1.1s var(--ease-spring) both;
  /* sibling-index() staggers the intro with zero bookkeeping (Chrome 138+);
     the inline --i is the fallback for everyone else */
  animation-delay: calc(0.15s + var(--i) * 70ms);
  animation-delay: calc(0.15s + (sibling-index() - 1) * 70ms);
}
/* Pointer-driven magnetism replaces a font-weight hover, which reflowed the
   whole line and made the neighbouring letters twitch. */
@media (hover: none) {
  .hero-title span { transform: none; }
}
@keyframes letter-in {
  from {
    opacity: 0;
    translate: 0 0.35em;
    filter: blur(14px) drop-shadow(0 6px 40px var(--glow));
  }
}

.hero-sub {
  margin-top: clamp(1.2rem, 3vh, 2rem);
  font-size: clamp(1.05rem, 2.4vw, 1.5rem);
  color: var(--ink);
  max-width: 34ch;
  text-wrap: balance;
  text-shadow: 0 2px 24px oklch(10% 0.02 285 / 0.9), 0 0 8px oklch(10% 0.02 285 / 0.6);
  animation: fade-up 1s var(--ease-out) 0.9s both;
}
.hero-hint {
  margin-top: 1.1rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  color: var(--ink-dim);
  text-shadow: 0 1px 14px oklch(10% 0.02 285 / 0.95);
  animation: fade-up 1s var(--ease-out) 1.25s both;
}
@keyframes fade-up {
  from { opacity: 0; translate: 0 24px; }
}

.scroll-cue {
  position: absolute;
  bottom: clamp(1.5rem, 5vh, 3rem);
  left: 50%;
  translate: -50%;
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  color: var(--ink-faint);
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  animation: fade-up 1s var(--ease-out) 1.6s both;
}
.wheel {
  width: 22px;
  height: 34px;
  border: 1.5px solid var(--ink-faint);
  border-radius: 12px;
  display: grid;
  justify-content: center;
  padding-top: 6px;
}
.wheel i {
  width: 2.5px;
  height: 7px;
  border-radius: 2px;
  background: var(--accent);
  animation: wheel 1.6s var(--ease-out) infinite;
}
@keyframes wheel {
  0% { translate: 0 0; opacity: 1; }
  70%, 100% { translate: 0 10px; opacity: 0; }
}

/* ---- ORIGINS: tall wrapper + sticky stage; particles assemble behind ---- */
.origins {
  max-width: none;
  padding: 0;
  height: 260vh;
  /* the --origins / --s-origins view timelines are declared in effects.css,
     alongside the rest of the scroll-spy, so the names stay in one place */
}
.origins-stage {
  position: sticky;
  top: 0;
  height: 100svh;
  display: grid;
  place-items: center;
  overflow: clip;
}
.origins-copy {
  position: relative;
  width: min(90vw, 46rem);
  height: 8rem;
  text-align: center;
}
/* soft scrim so phrases stay readable over the dense sigil */
.origins-copy::before {
  content: "";
  position: absolute;
  inset: -55% -18%;
  background: radial-gradient(closest-side, oklch(10% 0.02 285 / 0.6), transparent 75%);
  filter: blur(4px);
}
.phrase {
  position: absolute;
  inset: 0;
  /* Deliberately NOT grid or flex. Both blockify inline children into items,
     so "…was <em>an honor</em>." becomes three boxes — text, em, full stop —
     stacked on three rows. Block layout keeps it one inline flow; the vertical
     centring that place-content used to do is now align-content, which applies
     to block containers too (Chrome 123+). Without it the text simply sits at
     the top of the 8rem stage. */
  display: block;
  align-content: center;
  font-size: clamp(1.4rem, 3.6vw, 2.6rem);
  font-weight: 480;
  letter-spacing: -0.02em;
  text-wrap: balance;
  opacity: 0;
  text-shadow: 0 2px 30px oklch(10% 0.02 285 / 0.9);
}
.phrase em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  color: var(--accent);
}
/* Static fallback if scroll-driven animations are unsupported */
@supports not (animation-timeline: view()) {
  .origins { height: auto; padding: 20vh 1.5rem; }
  .origins-stage { position: static; height: auto; }
  .origins-copy { height: auto; display: grid; gap: 1.5rem; }
  .phrase { position: static; opacity: 1; }
}

/* ---- EXPLOITS: pure-CSS carousel with scroll markers + buttons ---- */
.exploits {
  max-width: none;
  padding-inline: 0;
  /* rise into the sigil's dispersal zone instead of trailing dead space */
  margin-top: -50vh;
}
@supports not (animation-timeline: view()) {
  .exploits { margin-top: 0; }
}
.exploits .section-head { padding-inline: clamp(1.25rem, 5vw, 4rem); max-width: 72rem; margin-inline: auto; width: 100%; }

.rail-wrap { position: relative; }

.rail {
  list-style: none;
  padding: 2rem calc(50vw - min(36vw, 17rem)) 3.5rem;
  display: flex;
  gap: clamp(1rem, 3vw, 2rem);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* marker-dot navigation glides instead of jumping */
  scroll-behavior: smooth;
  scrollbar-width: none;
  /* CSS carousel marker dots (Chrome 135+): zero JS, per-card precise */
  scroll-marker-group: after;
}
.rail::-webkit-scrollbar { display: none; }
.rail:focus { outline: none; }
/* The rail is a focusable scroll container; ring the wrapper rather than the
   full-bleed scroller so the affordance reads as one control. */
.rail-wrap:has(.rail:focus-visible) {
  outline: 2px solid oklch(from var(--accent) l c h / 0.55);
  outline-offset: -8px;
  border-radius: 1.6rem;
  corner-shape: squircle;
}
/* Drag-to-scroll. Snapping has to stand down while a drag is in flight or it
   fights every scrollLeft write; main.js re-snaps to the nearest card on release. */
.rail.dragging { scroll-snap-type: none; scroll-behavior: auto; }
body:not(.has-cursor) .rail { cursor: grab; }
body:not(.has-cursor) .rail.dragging { cursor: grabbing; }

/* Prev/next are real buttons: native ::scroll-button pages by
   scrollport (skipping cards on wide screens), so stepping exactly
   one card is handled by a few lines of JS in main.js. */
.rail-btn {
  position: absolute;
  top: calc(50% - 3.5rem);
  z-index: 5;
  display: grid;
  place-content: center;
  width: 3rem;
  aspect-ratio: 1;
  border-radius: 50%;
  corner-shape: squircle;
  border: 1px solid var(--glass-edge);
  background: var(--glass);
  backdrop-filter: blur(10px);
  color: var(--ink);
  font-size: 1.1rem;
  transition: border-color 0.25s, box-shadow 0.25s, opacity 0.25s;
}
.rail-btn:hover { border-color: var(--accent); box-shadow: 0 0 18px var(--glow); }
.rail-btn:disabled { opacity: 0.25; pointer-events: none; }
.rail-btn.prev { left: clamp(0.5rem, 3vw, 2.5rem); }
.rail-btn.next { right: clamp(0.5rem, 3vw, 2.5rem); }
/* small screens: swipe + markers are enough, buttons collide with cards */
@media (width < 720px) {
  .rail-btn { display: none; }
}

.rail::scroll-marker-group {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.6rem;
  padding: 1.25rem;
}
.card::scroll-marker {
  /* markers default to display:inline, which collapses an empty
     box to 0×0 — block + explicit size makes them visible */
  content: "";
  display: block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: oklch(from var(--ink-faint) l c h / 0.55);
  transition: background 0.3s, scale 0.3s;
}
.card::scroll-marker:hover { scale: 1.4; }
.card::scroll-marker:target-current {
  background: var(--accent);
  box-shadow: 0 0 10px var(--glow);
  scale: 1.25;
}

.card {
  scroll-snap-align: center;
  flex: 0 0 min(72vw, 34rem);
  padding: clamp(1.5rem, 3vw, 2.5rem);
  border-radius: 1.5rem;
  corner-shape: squircle;
  /* --focus (0..1) is scrubbed by the card's rail position — the
     centered card's frame ignites and casts an accent halo */
  border: 1px solid oklch(from var(--accent) 78% calc(c * 0.6) h / calc(0.15 + var(--focus) * 0.5));
  background:
    linear-gradient(160deg, oklch(from var(--accent) 30% calc(c * 0.3) h / 0.18), transparent 55%),
    var(--glass);
  backdrop-filter: blur(14px) saturate(1.3);
  box-shadow:
    0 24px 60px -30px oklch(5% 0.02 285 / 0.9),
    0 0 calc(var(--focus) * 48px) calc(var(--focus) * -8px)
      oklch(from var(--accent) l c h / 0.4);
}
.card .year {
  font-family: var(--font-mono);
  font-size: clamp(2rem, 5vw, 3.2rem);
  font-weight: 700;
  line-height: 1;
  background: linear-gradient(120deg, var(--accent-2), var(--accent-hot));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin-bottom: 0.8rem;
  filter: drop-shadow(0 0 calc(4px + var(--focus) * 14px)
    oklch(from var(--accent) l c h / calc(var(--focus) * 0.55)));
}
.card h3 {
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin-bottom: 0.7rem;
}
.card p { color: var(--ink-dim); font-size: 0.98rem; text-wrap: pretty; }
.card .tags { margin-top: 1.1rem; display: flex; flex-wrap: wrap; gap: 0.45rem; }
.card .tags span {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  padding: 0.3rem 0.65rem;
  border-radius: 999px;
  corner-shape: squircle;
  border: 1px solid oklch(from var(--accent) 70% c h / 0.25);
  color: var(--accent);
}

/* ---- NUMBERS: scroll-scrubbed pure-CSS counters ---- */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  gap: clamp(1rem, 2.5vw, 1.75rem);
}
.stat {
  padding: clamp(1.4rem, 2.5vw, 2rem);
  border-radius: 1.4rem;
  corner-shape: squircle;
  border: 1px solid var(--glass-edge);
  background: var(--glass);
  backdrop-filter: blur(12px);
}
.stat dt {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
  margin-bottom: 0.9rem;
}
.stat dd {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.num {
  font-size: clamp(2.6rem, 5vw, 3.8rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(120deg, oklch(96% 0.02 var(--hue)), var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
/* Fallback: show the final value as plain text */
.num::after { content: attr(data-n); }
.unit { font-family: var(--font-mono); font-size: 0.85rem; color: var(--accent); }
.stat .note { margin-top: 0.8rem; font-size: 0.82rem; color: var(--ink-faint); text-wrap: pretty; }

/* ---- CAPABILITIES ---- */
.cap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: clamp(1rem, 2.5vw, 1.75rem);
}
.cap {
  position: relative;
  isolation: isolate;
  overflow: clip;
  padding: clamp(1.5rem, 3vw, 2.2rem);
  border-radius: 1.5rem;
  corner-shape: squircle;
  border: 1px solid var(--glass-edge);
  background: var(--glass);
  backdrop-filter: blur(12px);
  transition: translate 0.45s var(--ease-spring), border-color 0.35s, box-shadow 0.35s;
  /* if() conditionals (Chrome 137+): tier decides the halo hue.
     The plain fallback is declared first. */
  --halo: var(--accent);
  --halo: if(style(--tier: 2): var(--accent-2); else: var(--accent));
}
.cap:hover {
  translate: 0 -6px;
  border-color: oklch(from var(--halo) l c h / 0.45);
  box-shadow: 0 24px 60px -24px oklch(from var(--halo) 40% c h / 0.5);
}
/* rotating conic sheen, revealed on hover */
.cap::before {
  content: "";
  position: absolute;
  inset: -40%;
  z-index: -1;
  background: conic-gradient(
    from var(--sheen),
    transparent 0 78%,
    oklch(from var(--halo) l c h / 0.18) 88%,
    transparent 98%
  );
  opacity: 0;
  transition: opacity 0.5s;
}
.cap:hover::before {
  opacity: 1;
  animation: sheen-spin 3.5s linear infinite;
}
@keyframes sheen-spin { to { --sheen: 360deg; } }

.cap .glyph {
  display: inline-grid;
  place-content: center;
  width: 3rem;
  aspect-ratio: 1;
  margin-bottom: 1.1rem;
  font-size: 1.4rem;
  color: var(--halo);
  border: 1px solid oklch(from var(--halo) l c h / 0.35);
  border-radius: 0.9rem;
  corner-shape: squircle;
  background: oklch(from var(--halo) 25% calc(c * 0.4) h / 0.25);
  text-shadow: 0 0 14px var(--halo);
}
.cap h3 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.6rem; letter-spacing: -0.01em; }
.cap p { color: var(--ink-dim); font-size: 0.95rem; text-wrap: pretty; }

/* ---- DOSSIER ---- */
.dossier .file {
  border: 1px solid var(--glass-edge);
  border-radius: 1.6rem;
  corner-shape: squircle;
  background:
    repeating-linear-gradient(0deg, transparent 0 34px, oklch(from var(--accent) 70% c h / 0.035) 34px 35px),
    var(--glass);
  backdrop-filter: blur(14px);
  overflow: clip;
}
.file-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.2rem clamp(1.2rem, 3vw, 2rem);
  border-bottom: 1px dashed oklch(from var(--accent) 70% c h / 0.25);
}
.stamp {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 0.8rem;
  letter-spacing: 0.3em;
  color: oklch(68% 0.22 25);
  border: 2px solid oklch(68% 0.22 25 / 0.7);
  padding: 0.35rem 0.8rem;
  rotate: -3deg;
  border-radius: 0.4rem;
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='40'%3E%3Cfilter id='r'%3E%3CfeTurbulence baseFrequency='0.6' numOctaves='2'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.9 0.1'/%3E%3CfeComposite operator='over' in2='SourceGraphic'/%3E%3C/filter%3E%3Crect width='120' height='40' fill='%23fff' filter='url(%23r)'/%3E%3C/svg%3E");
  mask-size: cover;
}
.declass {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  padding: 0.6rem 1.2rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  corner-shape: squircle;
  color: var(--accent);
  transition: background-color 0.3s, color 0.3s, box-shadow 0.3s;
  anchor-name: --declass;
}
.declass:hover {
  background: var(--accent);
  color: oklch(14% 0.03 285);
  box-shadow: 0 0 24px var(--glow);
}

/* Anchored tooltip (CSS Anchor Positioning) — appears on hover/focus */
.tip {
  position: fixed;
  position-anchor: --declass;
  position-area: block-start;
  position-try-fallbacks: flip-block;
  margin-block-end: 0.6rem;
  width: max-content;
  max-width: 16rem;
  padding: 0.5rem 0.8rem;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.05em;
  color: var(--ink);
  background: oklch(from var(--bg) calc(l + 0.06) c h / 0.9);
  border: 1px solid var(--glass-edge);
  border-radius: 0.6rem;
  opacity: 0;
  translate: 0 4px;
  transition: opacity 0.25s, translate 0.25s;
  pointer-events: none;
  z-index: 60;
}
.declass:hover + .tip, .declass:focus-visible + .tip {
  opacity: 1;
  translate: 0 0;
}
/* Hide entirely when anchors are unsupported (it would land at viewport edge) */
@supports not (anchor-name: --a) {
  .tip { display: none; }
}

.dossier details {
  border-top: 1px solid oklch(from var(--accent) 70% c h / 0.12);
}
.dossier summary {
  list-style: none;
  cursor: pointer;
  padding: 1.2rem clamp(1.2rem, 3vw, 2rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  transition: color 0.25s, background-color 0.25s;
}
.dossier summary::-webkit-details-marker { display: none; }
.dossier summary::before {
  content: "+";
  font-family: var(--font-mono);
  color: var(--accent);
  font-weight: 400;
  transition: rotate 0.4s var(--ease-spring);
}
.dossier details[open] summary::before { rotate: 45deg; }
.dossier summary:hover { color: var(--accent); background: oklch(from var(--accent) l c h / 0.05); }

/* Animate <details> open/close: ::details-content + interpolate-size */
.dossier details::details-content {
  block-size: 0;
  overflow: clip;
  transition: block-size 0.5s var(--ease-out), content-visibility 0.5s allow-discrete;
}
.dossier details[open]::details-content { block-size: auto; }
.d-body {
  padding: 0.2rem clamp(1.2rem, 3vw, 2rem) 1.6rem;
  color: var(--ink-dim);
  max-width: 60ch;
  text-wrap: pretty;
}

/* Redactions — declassified via the View Transitions API */
.redact {
  background: currentColor;
  color: transparent;
  border-radius: 0.2em;
  padding-inline: 0.15em;
  transition: background-color 0.4s, color 0.4s;
  view-transition-class: redaction;
}
.file.declassified .redact {
  background: oklch(from var(--accent) l c h / 0.14);
  color: var(--accent);
}
.file.declassified .stamp {
  color: oklch(72% 0.17 150);
  border-color: oklch(72% 0.17 150 / 0.7);
}
.file.declassified .stamp::before { content: "UN‑"; }
::view-transition-group(.redaction) {
  animation-duration: 0.6s;
}

.alias {
  font-family: inherit;
  color: var(--accent);
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  padding: 0;
}
.alias:hover { color: var(--accent-hot); }

/* Anchored popovers with @starting-style entry animation */
.popnote {
  position: fixed;
  position-area: block-start span-inline-end;
  position-try-fallbacks: flip-block, flip-inline;
  margin: 0.5rem;
  width: min(20rem, 80vw);
  padding: 0.9rem 1.1rem;
  font-size: 0.85rem;
  color: var(--ink-dim);
  background: oklch(from var(--bg) calc(l + 0.05) c h / 0.92);
  backdrop-filter: blur(16px);
  border: 1px solid oklch(from var(--accent) 70% c h / 0.35);
  border-radius: 1rem;
  corner-shape: squircle;
  box-shadow: 0 20px 50px -20px oklch(5% 0.02 285);
  opacity: 1;
  scale: 1;
  transition: opacity 0.35s var(--ease-out), scale 0.35s var(--ease-spring), display 0.35s allow-discrete, overlay 0.35s allow-discrete;
}
.popnote:popover-open {
  @starting-style {
    opacity: 0;
    scale: 0.85;
  }
}
.popnote:not(:popover-open) {
  opacity: 0;
  scale: 0.9;
}
.popnote .redact {
  background: oklch(from var(--accent) l c h / 0.14);
  color: var(--accent);
}

/* ---- SIGNALS: marquees ---- */
.signals { max-width: none; padding-inline: 0; }
.signals .section-head { padding-inline: clamp(1.25rem, 5vw, 4rem); max-width: 72rem; margin-inline: auto; width: 100%; }

.marquee {
  display: flex;
  overflow: clip;
  gap: 3rem;
  padding-block: 1.1rem;
  mask-image: linear-gradient(90deg, transparent, black 12%, black 88%, transparent);
  border-block: 1px solid oklch(from var(--accent) 70% c h / 0.08);
}
.marquee + .marquee { border-block-start: none; }
.marquee ul {
  list-style: none;
  padding: 0;
  display: flex;
  gap: 3rem;
  flex-shrink: 0;
  min-width: max-content;
  animation: marquee 40s linear infinite;
}
.marquee.rev ul { animation-direction: reverse; }
.marquee:hover ul { animation-play-state: paused; }
.marquee li {
  font-size: clamp(1rem, 2vw, 1.35rem);
  font-family: var(--font-serif);
  font-style: italic;
  white-space: nowrap;
  color: var(--ink);
}
.marquee li b {
  font-family: var(--font-mono);
  font-style: normal;
  font-weight: 400;
  font-size: 0.65em;
  letter-spacing: 0.08em;
  color: var(--accent);
  margin-left: 0.6em;
}
@keyframes marquee { to { translate: calc(-100% - 3rem) 0; } }
/* Explicit per-rail fallbacks first... */
.marquee[data-speed="42s"] ul { animation-duration: 42s; }
.marquee[data-speed="55s"] ul { animation-duration: 55s; }
/* ...then typed attr() (Chrome 133+) reads the speed straight off the element.
   Same specificity, later in source, so it wins wherever it parses at all. */
.marquee[data-speed] ul { animation-duration: attr(data-speed type(<time>), 40s); }

/* ---- COLOPHON ---- */
.scan {
  border: 1px solid var(--glass-edge);
  border-radius: 1.6rem;
  corner-shape: squircle;
  background: var(--glass);
  backdrop-filter: blur(12px);
  padding: clamp(1.4rem, 3vw, 2.2rem);
}
.scan-title {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  color: var(--accent);
  margin-bottom: 1.2rem;
}
/* which of the three render paths this visitor actually got */
.scan-backend {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.7;
  margin-bottom: 1.2rem;
  padding: 0.7rem 0.9rem;
  border-radius: 0.8rem;
  corner-shape: squircle;
  border: 1px solid oklch(from var(--ink-faint) l c h / 0.3);
  color: var(--ink-dim);
  text-wrap: pretty;
}
.scan-backend.is-live {
  border-color: oklch(70% 0.16 155 / 0.45);
  background: oklch(70% 0.16 155 / 0.07);
}
.scan-backend.is-fallback {
  border-color: oklch(75% 0.15 75 / 0.45);
  background: oklch(75% 0.15 75 / 0.07);
}

.scan-list {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}
.scan-list li {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  padding: 0.45rem 0.85rem;
  border-radius: 999px;
  corner-shape: squircle;
  border: 1px solid oklch(from var(--ink-faint) l c h / 0.3);
  color: var(--ink-faint);
}
.scan-list li.ok {
  border-color: oklch(70% 0.16 155 / 0.5);
  color: oklch(78% 0.14 155);
  background: oklch(70% 0.16 155 / 0.07);
}
.scan-list li::before {
  content: "—";
  opacity: 0.7;
}
.scan-list li.ok::before { content: "✓"; }
.scan-note { margin-top: 1.1rem; font-size: 0.8rem; color: var(--ink-faint); }

/* ---- FOOTER ---- */
.footer {
  text-align: center;
  padding: 5rem 1.5rem 6rem;
  color: var(--ink-dim);
  font-size: 0.85rem;
  text-shadow: 0 1px 12px oklch(10% 0.02 285 / 0.8);
}
.fin {
  font-family: var(--font-mono);
  letter-spacing: 0.5em;
  font-size: 0.7rem;
  color: var(--accent);
  margin-bottom: 1.4rem;
}
.fin::before, .fin::after { content: " ··· "; opacity: 0.5; }
.footer .fine { margin-top: 0.6rem; font-size: 0.72rem; opacity: 0.7; }
/* ============================================================
   MARLOWE — effects.css
   Scroll-driven animation choreography + accessibility guards.
   Everything here is progressive enhancement: without support,
   content is simply visible and static.
   ============================================================ */

/* Accent hue drifts 70° across the full page scroll — every
   accent-tinted element on the page shifts with it, and the GL
   scene reads the same progression. */
@supports (animation-timeline: scroll()) {
  :root {
    animation: hue-drift linear both;
    animation-timeline: scroll(root);
  }
  @keyframes hue-drift {
    to { --hue-shift: 70; }
  }

  /* Top progress bar, scrubbed by the scrollbar */
  .progress {
    animation: progress-grow linear both;
    animation-timeline: scroll(root);
  }
  @keyframes progress-grow {
    from { scale: 0 1; }
    to { scale: 1 1; }
  }

  /* ---- Scroll-spy without a scroll-spy ----
     Every section publishes a named view timeline, :root pulls those names
     into scope, and each nav link animates --on from the timeline matching
     its own href. Both the header rail and the compact index sheet read the
     same signal. No IntersectionObserver, no scroll listener, no JS. */
  :root {
    timeline-scope:
      --origins, --s-origins, --s-exploits, --s-numbers,
      --s-capabilities, --s-dossier, --s-signals, --s-colophon;
  }
  /* the origins stage also drives its own phrase sequence, hence two names */
  #origins      { view-timeline: --origins block, --s-origins block; }
  #exploits     { view-timeline: --s-exploits block; }
  #numbers      { view-timeline: --s-numbers block; }
  #capabilities { view-timeline: --s-capabilities block; }
  #dossier      { view-timeline: --s-dossier block; }
  #signals      { view-timeline: --s-signals block; }
  #colophon     { view-timeline: --s-colophon block; }

  .nav-rail a {
    animation: nav-here linear both;
    animation-range: cover 0% cover 100%;
  }
  /* the plateau is wide enough that neighbours only ever cross-fade */
  @keyframes nav-here {
    0%, 28%   { --on: 0; }
    42%, 66%  { --on: 1; }
    80%, 100% { --on: 0; }
  }
  .nav-rail a[href="#origins"]      { animation-timeline: --s-origins; }
  .nav-rail a[href="#exploits"]     { animation-timeline: --s-exploits; }
  .nav-rail a[href="#numbers"]      { animation-timeline: --s-numbers; }
  .nav-rail a[href="#capabilities"] { animation-timeline: --s-capabilities; }
  .nav-rail a[href="#dossier"]      { animation-timeline: --s-dossier; }
  .nav-rail a[href="#signals"]      { animation-timeline: --s-signals; }
  .nav-rail a[href="#colophon"]     { animation-timeline: --s-colophon; }

  /* Scroll cue fades as soon as you commit to scrolling */
  .scroll-cue {
    animation:
      fade-up 1s var(--ease-out) 1.6s both,
      cue-out linear both;
    animation-timeline: auto, scroll(root);
    animation-range: normal, 0 22vh;
  }
  @keyframes cue-out {
    to { opacity: 0; translate: -50% 20px; }
  }
  .hero-hint {
    animation:
      fade-up 1s var(--ease-out) 1.25s both,
      hint-out linear both;
    animation-timeline: auto, scroll(root);
    animation-range: normal, 0 18vh;
  }
  @keyframes hint-out {
    to { opacity: 0; }
  }

  /* Hero recedes into the field as you leave it */
  .hero-title {
    animation: hero-out linear both;
    animation-timeline: view();
    animation-range: exit 0% exit 85%;
  }
  .hero-sub {
    animation:
      fade-up 1s var(--ease-out) 0.9s both,
      hero-out linear both;
    animation-timeline: auto, view();
    animation-range: normal, exit 0% exit 85%;
  }
  .eyebrow {
    animation:
      fade-up 0.9s var(--ease-out) 0.05s both,
      hero-out linear both;
    animation-timeline: auto, view();
    animation-range: normal, exit 0% exit 85%;
  }
  @keyframes hero-out {
    to {
      opacity: 0;
      translate: 0 -14vh;
      scale: 0.92;
      filter: blur(10px);
    }
  }

  /* Generic reveals: rise + deblur while entering the viewport */
  .reveal {
    animation: reveal linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 55%;
  }
  @keyframes reveal {
    from {
      opacity: 0;
      translate: 0 3.5rem;
      filter: blur(8px);
    }
  }

  /* Origins phrases: each phrase owns a slice of the named
     --origins view timeline (ranges set inline via --r0/--r1) */
  .phrase {
    animation: phrase linear both;
    animation-timeline: --origins;
    animation-range: contain var(--r0) contain var(--r1);
  }
  @keyframes phrase {
    0% { opacity: 0; translate: 0 2.2rem; filter: blur(6px); }
    14% { opacity: 1; translate: 0 0; filter: blur(0); }
    86% { opacity: 1; translate: 0 0; filter: blur(0); }
    100% { opacity: 0; translate: 0 -2.2rem; filter: blur(6px); }
  }

  /* Exploit cards: coverflow scrubbed by their own inline position */
  .card {
    animation: coverflow linear both;
    animation-timeline: view(inline);
    animation-range: cover 0% cover 100%;
  }
  @keyframes coverflow {
    0% {
      transform: perspective(1200px) rotateY(14deg) scale(0.88);
      opacity: 0.45;
      filter: blur(4px) saturate(0.7);
      --focus: 0;
    }
    50% {
      transform: none;
      opacity: 1;
      filter: none;
      --focus: 1;
    }
    100% {
      transform: perspective(1200px) rotateY(-14deg) scale(0.88);
      opacity: 0.45;
      filter: blur(4px) saturate(0.7);
      --focus: 0;
    }
  }

  /* Pure-CSS counters: @property-typed --n tweens from 0 to the
     inline --target, scrubbed by viewport entry; a counter() turns
     the number into rendered text. Zero JavaScript. */
  .num {
    counter-reset: n var(--n);
    animation: count linear both;
    animation-timeline: view();
    animation-range: entry 30% contain 45%;
  }
  .num::after {
    content: counter(n);
  }
  @keyframes count {
    from { --n: 0; }
    to { --n: var(--target); }
  }

  /* Marquees drift slightly faster while their section crosses the
     viewport (view-timeline driving translate on the wrapper) */
  .marquee {
    animation: marquee-skew linear both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
  @keyframes marquee-skew {
    from { translate: 4vw 0; }
    to { translate: -4vw 0; }
  }
}

/* ---- Reduced motion: honor it everywhere ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* Scrubbed animations are user-driven, but blur/parallax can
     still be disorienting — pin everything readable. */
  .reveal, .phrase, .card, .hero-title, .hero-sub, .eyebrow {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    translate: none !important;
    filter: none !important;
  }
  .num { animation: none !important; }
  .num::after { content: attr(data-n); }
  .marquee ul { animation-play-state: paused !important; }
  .grain { animation: none !important; }
  html { scroll-behavior: auto; }
  .rail { scroll-behavior: auto; }
  .scroll-cue { display: none; }
  /* These two are position readouts, not motion — the blanket duration
     override above would otherwise collapse their scrubbed range. */
  .progress, .nav-rail a { animation-duration: auto !important; }
}

/* ---- Reduced transparency: swap the glass for solid surfaces ---- */
@media (prefers-reduced-transparency: reduce) {
  .nav-inner, .audio-toggle, .menu-btn, .menu-sheet, .card, .stat,
  .cap, .dossier .file, .popnote, .scan, .rail-btn, .tip {
    backdrop-filter: none !important;
    background: oklch(14% 0.025 285 / 0.97) !important;
  }
  .grain, .vignette { display: none; }
}

/* ---- Forced colors: let the OS palette win, drop the decoration ---- */
@media (forced-colors: active) {
  #gl, .grain, .vignette, #cursor { display: none !important; }
  .card, .stat, .cap, .dossier .file, .menu-sheet, .scan, .popnote {
    border: 1px solid CanvasText;
    background: Canvas;
    backdrop-filter: none;
  }
  .num, .card .year, h2 em, .hero-sub em, .hero-title span {
    -webkit-text-fill-color: CanvasText;
    color: CanvasText;
    background: none;
    filter: none;
  }
  .redact { background: CanvasText; color: Canvas; forced-color-adjust: none; }
  .file.declassified .redact { background: Canvas; color: CanvasText; }
  .progress { background: Highlight; }
}

/* ---- Print: the dossier becomes an actual paper file ---- */
@media print {
  @page { margin: 18mm; }
  #gl, .grain, .vignette, #cursor, .nav, .menu-sheet, .scroll-cue,
  .hero-hint, .rail-btn, .skip-link, .declass, .tip, .origins-copy::before,
  .marquee ul[aria-hidden="true"] { display: none !important; }
  /* carousel dots are navigation chrome; paper does not scroll */
  .rail::scroll-marker-group, .card::scroll-marker { display: none !important; }
  /* pseudo-elements need naming explicitly — `*` alone leaves their paint behind */
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    filter: none !important;
    backdrop-filter: none !important;
    text-shadow: none !important;
    box-shadow: none !important;
    color: #111 !important;
    background: transparent !important;
  }
  body { background: #fff !important; }
  .hero-title span, .num, .card .year, h2 em, .hero-sub em {
    -webkit-text-fill-color: #111 !important;
  }
  section { padding: 0 0 1.5rem; page-break-inside: avoid; max-width: none; }
  .hero { min-height: auto; padding-block: 0 2rem; }
  .origins { height: auto; }
  .origins-stage { position: static; height: auto; overflow: visible; }
  .origins-copy { height: auto; display: grid; gap: 0.5rem; }
  .phrase { position: static; display: block; text-align: center; opacity: 1 !important; }
  /* the sticky stage is gone, so the overlap this compensated for is too */
  .exploits { margin-top: 0 !important; }
  /* the counters are scrubbed by scroll and would otherwise all print as 0 */
  .num::after { content: attr(data-n) !important; }
  /* the carousel flattens into a printable list */
  .rail {
    display: block;
    overflow: visible;
    padding: 0;
    columns: 2;
    column-gap: 1.5rem;
  }
  .card {
    break-inside: avoid;
    border: 1px solid #bbb;
    margin-bottom: 1rem;
    padding: 0.8rem;
  }
  .stats, .cap-grid { display: block; columns: 2; column-gap: 1.5rem; }
  .stat, .cap { break-inside: avoid; border: 1px solid #bbb; margin-bottom: 1rem; }
  .stamp { border-color: #111; mask-image: none; }
  .cap .glyph, .card .tags span { border-color: #bbb; }
  /* Every fold of the file opens on paper; redactions stay redacted.
     These are an exclusive accordion (name="dossier"), so forcing [open]
     from script would just make each fold close the last one — overriding
     the UA's content-visibility on ::details-content is the way in. */
  .dossier details::details-content {
    block-size: auto !important;
    content-visibility: visible !important;
    overflow: visible !important;
  }
  .dossier summary::before { content: "" !important; }
  .dossier details { border-top: 1px solid #bbb; }
  .redact { background: #111 !important; color: #111 !important; }
  /* both rails carry different quotes; only the duplicated track is dropped */
  .marquee { display: block; mask-image: none; border: none; padding-block: 0; }
  .marquee ul { display: block; animation: none !important; min-width: 0; }
  .marquee li { white-space: normal; }
  .scan { display: none; }
}

/* ---- View transition choreography for declassification ---- */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.5s;
}
