/* ============================================================
   THE BUREAU OF DECEIVERS — optical, blocky, square-modular.
   Two worlds, one page:
     · the opening — darkness, the spirograph, the forming poster
     · the mosaic — a continuous grid of ink/paper blocks the
       poster docks into. Pure HTML/CSS below the fold: illusions
       are gradients, entrances are CSS scroll-driven animations,
       the hover misregistration is plain :hover. JS exists only
       where it IS the piece (spirograph, halftone, scanline
       title, the formation scrub).
   ============================================================ */

/* ============================================================
   TOKENS
   ============================================================ */
:root {
  /* The two surfaces of the mosaic */
  --ink:   #131111;
  --paper: #ede8e8;
  --mid:   #55504c;      /* secondary text on paper */
  --mid-p: #c3beb7;      /* secondary text on ink (AA on the field) */
  --mortar:#a8a29c;      /* exhibit mortar grey */

  /* Bureau red — reserved for the pulse dot */
  --red: #e03a3e;

  /* Misregistration plates (transient colour only) */
  --plate-c: rgba(0, 136, 176, 0.6);
  --plate-m: rgba(214, 0, 108, 0.6);

  /* Opening (dark world) text */
  --text-primary:   oklch(94% 0.005 85);
  --c-teal:         oklch(72% 0.065 212);

  /* Layout */
  --nav-h: 42px;
  --gap: 12px;

  /* Type */
  --serif: 'Operator SSm', Georgia, serif;               /* the speaking voice */
  --mono:  'Operator Mono', 'Courier New', monospace;    /* the typed voice */
  --font-body: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;

  /* ── Type presets — the eight voices of the site.
     Media queries retune THESE, never individual selectors. ── */
  --t-display: clamp(3.2rem, 8.5vw, 9.6rem);   /* the question */
  --t-title-1: clamp(2.2rem, 4.2vw, 4.6rem);   /* hero, findings, case titles */
  --t-title-2: clamp(1.9rem, 3vw, 3.6rem);     /* card titles */
  --t-title-3: clamp(1.6rem, 2.4vw, 2.8rem);   /* CTA line, rail numerals */
  --t-lead:    clamp(1.3rem, 1.8vw, 2rem);     /* statements, archive line */
  --t-body:    clamp(1rem, 1.15vw, 1.25rem);   /* reading text (16px floor) */
  --t-caption: 0.85rem;                        /* notes, replies, art caption */
  --t-label:   0.72rem;                        /* buttons, meta, footer, nav */
  --t-stamp:   clamp(0.85rem, 1vw, 1rem);      /* kickers (file stamps) */

  --lh-display: 1.05;
  --lh-body:    1.5;
  --ls-display: -0.03em;
  --ls-label:   0.18em;
  --ls-stamp:   0.24em;

  /* Easing */
  --ease-expo: cubic-bezier(0.16, 1, 0.3, 1);

  /* Formation scrub (written by js/posterize.js) */
  --p: 0;
  --tiles: 0;
  --fade-out: 1;
  --face-in: 0;
  --title-s: 1;
  --poster-w: 400px;
  --poster-h: 556px;
  --dock-x: 0px;
  --dock-y: 0px;
}

/* ============================================================
   RESET & BASE
   ============================================================ */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  scroll-behavior: smooth;
  background-color: #101014;
}

body {
  background-color: transparent;
  color: var(--text-primary);
  font-family: var(--serif);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

a { color: inherit; text-decoration: none; }
button { font: inherit; }

/* ============================================================
   BACKGROUND LAYERS
   ============================================================ */

/* Halftone field — WebGL, the darkness the flower draws in */
#bg-cv {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

/* Scanlines — the whole page is a transmission */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  background: repeating-linear-gradient(
    to bottom,
    transparent,
    transparent 1px,
    oklch(0% 0 0 / 0.016) 1px,
    oklch(0% 0 0 / 0.016) 2px
  );
  pointer-events: none;
  z-index: 999;
}

/* ============================================================
   NAV — the menu stays on top
   ============================================================ */
.nav {
  /* absolute, not fixed: inside the pinned hero on the home page
     (rides the narrative, scrolls away with it), plain top-of-page
     on the subpages */
  position: absolute;
  top: 0;
  left: var(--gap);
  right: var(--gap);
  z-index: 50;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding: 0 20px;
  /* a slim stripe of the same material as the dark blocks: flush
     with the top edge, inset at the sides, one block-gap above
     the poster. Appears only once the poster is formed (the
     nav-ready class - static on subpages, scrub-driven on home) */
  color: var(--paper);
  /* the type rides the flower from the first frame; the slab
     itself arrives with the poster (body.nav-ready) */
  background: transparent;
  transition: background 0.5s ease;
}
body.nav-ready .nav { background: rgba(0, 0, 0, 0.5); }

.nav-brand {
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 700;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
}

.nav-links {
  display: flex;
  gap: clamp(14px, 2.4vw, 28px);
  font-family: var(--mono);
  font-size: var(--t-label);
  font-style: italic;
}
.nav-links a { transition: opacity 0.2s ease; white-space: nowrap; }
.nav-links a[aria-current="page"] { display: none; }

/* Social marks: icons in the bar, icon + word in the panel */
.nav-soc { display: inline-flex; align-items: center; gap: 8px; }
.nav-soc svg { width: 15px; height: 15px; display: block; }
.soc-label { display: none; }

/* Mobile-only line under the hero CTAs: the immersive layer is a
   promise here, not a button (you cannot enter from a phone) */
.vision-note { display: none; }

/* The burger exists only on phones */
.nav-burger { display: none; }

/* mobile-only line breaks */
.m-br { display: none; }
.nav-links a:hover,
.nav-links a:focus-visible { opacity: 0.55; }

/* ============================================================
   OPENING — pinned region; the formation scrub
   ============================================================ */
#hero-scroll {
  position: relative;
  z-index: 1;
  /* formation (0–.66) + band build (.56–.96). Short on purpose: the
     magnet completes the poster after a small push, and the region
     collapses the moment it is formed (see js/posterize.js). */
  height: 200vh;
}

#hero-scroll #hero {
  position: sticky;
  top: 0;
  height: 100svh;
  /* overflow stays visible: clipping would slice the poster's
     cast shadow */
  overflow: visible;
}

/* ── The hero unit: poster left, full height; text beside it ── */
.hero-band {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: stretch;
  gap: clamp(24px, 4vw, 72px);
  padding: calc(var(--nav-h) + var(--gap)) var(--gap) var(--gap);
  pointer-events: none;
}
#hero.settled .hero-band { pointer-events: auto; }

/* The dock target: left margin, whole vertical space */
.hero-poster-slot {
  height: 100%;
  aspect-ratio: 0.72;
  flex: none;
  /* on portrait-ish desktop windows the height-driven slot could
     eat the row and push the text out - never wider than half */
  max-width: 52vw;
}

/* Text appears on the shared clock with the RGB reveal:
   split plates collapse as --tt reaches 1, then stay sharp */
.hero-band .hero-unit {
  --tt: clamp(0, calc((var(--tiles, 0) - var(--d, 0)) * var(--dinv, 1)), 1);
  opacity: var(--tt);
  transform: translate(calc(var(--tx, 0px) * (1 - var(--tt))), 0);
}
.hero-unit :is(.kicker, .hero-title, .hero-sub, .cta-alt) {
  text-shadow:
    calc(10px * (1 - var(--tt, 0))) 0 var(--plate-m),
    calc(-10px * (1 - var(--tt, 0))) 0 var(--plate-c);
}

.hero-unit {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  text-align: left;
  gap: clamp(12px, 1.8vh, 20px);
  padding-right: clamp(12px, 3vw, 60px);
  color: var(--paper);
}
.hero-unit .kicker { color: var(--mid-p); }
.hero-kicker { font-size: calc(var(--t-stamp) * 1.18); letter-spacing: var(--ls-stamp); }
.hero-title {
  font-family: var(--mono);
  font-weight: 700;
  font-size: var(--t-title-1);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  color: var(--paper);
}
.hero-title em { font-weight: 400; font-style: italic; }
.hero-sub {
  font-family: var(--serif);
  font-size: var(--t-lead);   /* promoted: reads at statement size */
  line-height: 1.45;
  color: var(--mid-p);
  max-width: 46ch;
}
.hero-sub strong { color: var(--paper); font-weight: 600; }
.hero-ctas { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.hero-unit .cta-alt { color: var(--mid-p); }
.hero-unit .claim-reply { color: var(--mid-p); }

/* ── The poster slot: carries the dock drift ── */
.poster-slot {
  position: absolute;
  inset: 0;
  z-index: 4;
  transform: translate(
    calc(var(--dock-x) * var(--p)),
    calc(var(--dock-y) * var(--p)));
  pointer-events: none;
}

/* The frame that becomes the poster */
.poster {
  position: absolute;
  inset: 0;
  margin: auto;
  overflow: hidden;
  z-index: 2;
  width:  calc(100%  * (1 - var(--p)) + var(--poster-w) * var(--p));
  height: calc(100% * (1 - var(--p)) + var(--poster-h) * var(--p));
  /* the sheet does not exist during the travel: the flower glides
     alone over the living background, then a sheet of PHOTOGRAPHIC
     PAPER materialises beneath it (face-in) - and the developing
     bath floods the paper, resolving the plate */
  background: rgb(237 232 232 / var(--face-in, 0));
  box-shadow:
    0 calc(6px  * var(--face-in, 0)) calc(18px * var(--face-in, 0)) oklch(0% 0 0 / calc(0.4 * var(--face-in, 0))),
    0 calc(20px * var(--face-in, 0)) calc(44px * var(--face-in, 0)) oklch(0% 0 0 / calc(0.3 * var(--face-in, 0))),
    inset 0 0 0 calc(1px * var(--face-in, 0)) oklch(100% 0 0 / 0.07);
}

/* Silk sheen inside the poster only */
.poster::before {
  content: '';
  position: absolute;
  inset: -20%;
  background:
    linear-gradient(118deg, transparent 38%, oklch(48% 0.10 262 / 0.18) 50%, transparent 62%),
    linear-gradient(295deg, transparent 55%, oklch(40% 0.08 268 / 0.12) 70%, transparent 85%);
  opacity: var(--face-in, 0);
  pointer-events: none;
}

/* Spirograph keeps its buffer aspect — the flower never distorts.
   As the photographic paper materialises (face-in) the drawing
   SINKS INTO IT - a latent image on blank paper, returned by the
   developer. (Also hides the construction ghosts, which read as
   grey circles on cream.) */
.spiro-wrap {
  position: absolute;
  top: calc(var(--spiro-top, 0px) * var(--p));
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% * (1 - var(--p)) + var(--spiro-w, 100%) * var(--p));
  aspect-ratio: var(--cv-ar, 1.6);
  z-index: 1;
  opacity: calc(1 - var(--face-in, 0));
}

#cv { display: block; width: 100%; height: 100%; }

/* Centre dot */
.hero-dot {
  position: absolute;
  top: 44%;   /* the flower's centre rides high (spirograph CY) */
  left: 50%;
  transform: translate(-50%, -50%);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #ff0000;
  z-index: 5;
  animation: dot-blink 1.5s ease-in-out infinite;
}

/* ── Fullscreen overlay (dissolves as the frame closes) ── */
.hero-inner {
  position: absolute;
  inset: 0;
  z-index: 3;
}

.hero-decl {
  position: absolute;
  z-index: 10;
  top: 55%;                /* 22M — the title band (21% tall) */
  left: 50%;
  transform: translateX(-50%) scale(var(--title-s));
  transform-origin: top center;
  /* the opening is textless: the title belongs to the poster and
     arrives with the other faces */
  opacity: var(--face-in, 0);
}

#title-cv { display: block; opacity: 0; }
#title-cv-mobile { display: none; opacity: 0; }
@media (max-width: 600px) {
  #title-cv { display: none; }
  #title-cv-mobile { display: block; }
}

/* The stem: a hairline rising from the page bottom toward the
   flower, stopping well short of it — the scroll suggestion */
.scroll-pip {
  position: absolute;
  bottom: clamp(18px, 3vh, 30px);
  left: 50%;
  transform: translateX(-50%);
  width: 1px;
  height: 192px;  /* the original pip, six times its first size */
  background: var(--c-teal);
  opacity: 0;
}

/* Stem reveal (after the first curve) */
#hero.hero-revealed .scroll-pip {
  animation: fadein 1s var(--ease-expo) 0.8s forwards,
             pipbob 2.6s ease-in-out 1.8s infinite;
}

/* Frozen poster: the dot holds solid */
#hero.frozen .hero-dot {
  animation: none !important;
  opacity: 1 !important;
}

/* The stem dissolves as the frame closes in */
#hero.scrubbing .scroll-pip {
  animation: none;
  opacity: var(--fade-out);
}

/* ── Poster face ── */
.poster-face {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  opacity: var(--face-in);
  text-align: center;
  font-family: var(--font-body);
  text-transform: uppercase;
  pointer-events: none;
}
.poster-billing {
  top: 84%;
  /* zone: 84-89% */
  width: 88%;
  font-size: clamp(0.55rem, calc(var(--poster-w) * 0.0145), 0.62rem);
  letter-spacing: 0.16em;
  line-height: 1.9;
  color: oklch(76% 0.07 212 / 0.92);   /* the strap's light-blue */
}
.poster-release {
  top: 78.5%;
  width: 92%;
  font-size: clamp(0.62rem, calc(var(--poster-w) * 0.021), 0.9rem);
  letter-spacing: 0.2em;
  line-height: 1.4;
  color: oklch(76% 0.07 212 / 0.92);
}

/* Partner marks — a single row of small white logos, the last
   band of the sheet. Rendered from ADS_CONFIG.poster.logos, so
   the strip simply stays empty until real files exist. */
.poster-logos {
  top: 92.5%;
  width: 88%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, calc(var(--poster-w) * 0.035), 30px);
}
.poster-logos img {
  height: clamp(9px, calc(var(--poster-h) * 0.022), 20px);
  width: auto;
  opacity: 0.72;
}
.poster-logos:empty { display: none; }

/* ============================================================
   THE CARDS — one topic per block (Apple grammar, Bureau ink)
   ============================================================ */
.cards {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
  padding: 0 var(--gap) var(--gap);
}

.card {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  min-height: clamp(480px, 72svh, 640px);
  padding: clamp(44px, 7vh, 84px) clamp(20px, 3vw, 48px) 0;
  gap: clamp(14px, 2vh, 24px);
}
.c-ink   { background: rgba(0, 0, 0, 0.5); color: var(--paper); }
.c-paper { background: var(--paper); color: var(--ink);
           box-shadow: inset 0 0 0 1px oklch(0% 0 0 / 0.06); }
.card-full { grid-column: 1 / -1; min-height: clamp(560px, 84svh, 760px); }
.card-half { min-height: clamp(460px, 68svh, 620px); }
/* Text-only cards size to their content */
.card-compact {
  min-height: 0;
  justify-content: center;
  padding-bottom: clamp(44px, 7vh, 84px);
}

/* The case page: room under the fixed nav; a taller opening card */
.page-cards { padding-top: calc(var(--nav-h) + var(--gap)); }
.page-hero { min-height: min(78svh, 700px); }
.page-hero .hero-ctas { justify-content: center; }
.case-decl { display: flex; justify-content: center; margin: clamp(8px, 1.6vh, 22px) 0; }
#case-title-cv { display: block; opacity: 0; max-width: 100%; }
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.card-copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 1.5vh, 18px);
  max-width: 62ch;
}
.card-h2 {
  font-family: var(--mono);
  font-weight: 700;
  font-size: var(--t-title-2);
  line-height: 1.08;
  letter-spacing: var(--ls-display);
}
.card-sub {
  font-family: var(--serif);
  font-size: var(--t-lead);   /* promoted: reads at statement size */
  line-height: 1.45;
  color: var(--mid);
  max-width: 52ch;
}
.c-ink .card-sub { color: var(--mid-p); }
.c-ink .card-sub strong, .c-ink .card-sub em { color: var(--paper); }
.card-sub strong { font-weight: 600; }

/* The claim card: copy left, spatial stand-in right */
.card-split {
  flex-direction: row;
  align-items: center;
  text-align: left;
  gap: clamp(24px, 4vw, 80px);
  padding-bottom: clamp(44px, 7vh, 84px);
}
.card-split .card-copy { flex: 1.5; align-items: flex-start; max-width: none; }
.card-split .card-copy .card-h2 { max-width: none; }
.card-art3d {
  flex: 1;
  display: grid;
  place-items: center;
  perspective: 1000px;
}
/* The morphing wireframe — sphere, cube, one-sheet; frameless,
   always turning, glitching through each transformation */
.wire3d {
  --wd: clamp(160px, 22vw, 300px);
  width: var(--wd);
  aspect-ratio: 1;
  transform-style: preserve-3d;
  animation: spin3d 16s linear infinite,
             wire-glitch 21s step-end infinite;
}
.w-face {
  position: absolute;
  inset: 0;
  border: 1px solid oklch(100% 0 0 / 0.55);
}
.w-f1 { animation: morph-1 21s ease-in-out infinite; }
.w-f2 { animation: morph-2 21s ease-in-out infinite; }
.w-f3 { animation: morph-3 21s ease-in-out infinite; }
.w-f4 { animation: morph-4 21s ease-in-out infinite; }
.w-f5 { animation: morph-5 21s ease-in-out infinite; }
.w-f6 { animation: morph-6 21s ease-in-out infinite; }

/* The visual fills the card's lower half */
.card-art {
  margin-top: auto;
  align-self: stretch;
  height: clamp(180px, 34svh, 320px);
  display: flex;
  flex-direction: column;
}
.card-art-center { align-items: center; justify-content: center; }
.card-art .linefield { flex: 1; }

/* ── The misregistration: reserved for the interactable —
   the exhibits (the visual puzzles), never whole blocks ── */
@media (hover: hover) and (pointer: fine) {
  .card-exhibit { transition: filter 0.12s ease; cursor: pointer; }
  .card-exhibit:hover {
    filter: invert(1);
    box-shadow: 5px 0 0 var(--plate-c), -5px 0 0 var(--plate-m);
    z-index: 6;
  }
}

/* ── The RGB reveal: every text arrives split, then locks in
   focus (scroll-driven; degrades to plain visible) ── */
/* ── Reveals (driven by js/reveal.js — one-shot, every engine) ──
   Text arrives on the RGB split; blocks rise. Without JS nothing
   is ever hidden. */
.rgb-pending { opacity: 0; }
.rgb-reveal  { animation: rgb-in 0.7s ease-out both; }

.cards .card {
  transition: opacity 0.7s ease, transform 0.8s var(--ease-expo);
}
.cards .card.card-pending {
  opacity: 0;
  transform: translateY(44px);
}

/* ============================================================
   SHARED TYPE
   ============================================================ */
.kicker {
  font-family: var(--mono);
  font-size: var(--t-stamp);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: var(--ls-stamp);
  text-transform: uppercase;
  color: var(--mid);
}
.c-ink .kicker { color: var(--mid-p); }

/* Metadata row */
.meta-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 36px;
}
.meta-row div { display: flex; flex-direction: column; gap: 3px; }

/* Credits: a single centred register under the copy (the owner's
   call: the one-column mobile look, everywhere) */
.meta-credits {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-content: center;
  gap: clamp(20px, 3vh, 34px) clamp(36px, 5vw, 84px);
  margin-top: clamp(10px, 2vh, 24px);
  text-align: center;
}
.meta-credits div { display: flex; flex-direction: column; gap: 6px; align-items: center; }
.meta-credits dt { font-size: var(--t-label); letter-spacing: var(--ls-label); }
.meta-credits dd {
  font-size: var(--t-body);
  font-weight: 600;
  line-height: 1.45;
  max-width: 34ch;
}
@media (max-width: 940px) {
  .meta-credits { grid-template-columns: minmax(0, 1fr); }
}
/* In a half card the 2x2 register has no room - one centred column */
.card-half .meta-credits { grid-template-columns: minmax(0, 1fr); }

/* Personnel: a film credit roll - roles right, names left, around
   a centre gutter. Shrinks with the card instead of overflowing. */
.meta-personnel {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px clamp(16px, 2.6vw, 44px);
  margin-top: clamp(10px, 2vh, 20px);
  width: 100%;
  max-width: 880px;
  margin-left: auto;
  margin-right: auto;
}
.meta-personnel div { display: contents; }
.meta-personnel dt { text-align: right; align-self: first baseline; }
.meta-personnel dd {
  text-align: left;
  align-self: first baseline;
  font-size: var(--t-body);
  font-weight: 600;
  line-height: 1.4;
  overflow-wrap: break-word;
}
.credits-sec { text-align: center; }
.meta-row dt {
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--mid);
}
.meta-row dd { font-weight: 600; }
.c-ink .meta-row dt { color: var(--mid-p); }

/* ============================================================
   EXHIBITS — illusions as pure CSS
   ============================================================ */
.exhibit-caption {
  padding: 10px 14px;
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--paper);
}
/* Exhibit C — line interference: the lines are not moving */
.linefield {
  flex: 1;
  min-height: 0;
  position: relative;
  background: repeating-linear-gradient(90deg, var(--paper) 0 3px, transparent 3px 14px);
  overflow: hidden;
}
.linefield::after {
  content: '';
  position: absolute;
  inset: -10%;
  background: repeating-linear-gradient(90deg, var(--ink) 0 3px, transparent 3px 13px);
  animation: field-drift 9s ease-in-out infinite;
}
/* The register slips: now and then the plates come apart and
   hunt for focus - converging, landing ('there we go'), then
   letting go again. Two events per cycle, never continuous. */
.linefield::before {
  content: '';
  position: absolute;
  inset: -10%;
  z-index: 1;
  background:
    repeating-linear-gradient(90deg, var(--plate-m) 0 3px, transparent 3px 13px),
    repeating-linear-gradient(90deg, var(--plate-c) 0 3px, transparent 3px 13px);
  mix-blend-mode: screen;
  opacity: 0;
  animation: field-glitch 11s linear infinite;
}
@keyframes field-glitch {
  0%, 54%  { opacity: 0;    background-position: 0 0, 0 0; }
  56%      { opacity: 0.85; background-position: -8px 0, 8px 0; }
  60%      { opacity: 0.85; background-position: -3px 0, 3px 0; }
  64.5%    { opacity: 0.9;  background-position: -1px 0, 1px 0; }
  66.5%    { opacity: 0;    background-position: 0 0, 0 0; }
  78%      { opacity: 0;    background-position: 0 0, 0 0; }
  79.5%    { opacity: 0.8;  background-position: 6px 0, -6px 0; }
  81.5%    { opacity: 0;    background-position: 0 0, 0 0; }
  100%     { opacity: 0;    background-position: 0 0, 0 0; }
}

/* Moiré — concentric interference */
.moire { position: absolute; inset: 0; }
.moire::before,
.moire::after {
  content: '';
  position: absolute;
  inset: -25%;
  background: repeating-radial-gradient(circle at 50% 50%, var(--ink) 0 5px, var(--paper) 5px 11px);
}
.moire::after {
  background: repeating-radial-gradient(circle at 50% 50%, var(--ink) 0 5px, transparent 5px 11px);
  mix-blend-mode: screen;
  opacity: 0.9;
  animation: moire-drift 8s ease-in-out infinite;
}

/* Blinking eye */
.eye {
  width: 52%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--paper);
  display: grid;
  place-items: center;
  animation: eye-blink 5.2s ease-in-out infinite;
}
.pupil {
  width: 42%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--ink);
}
/* ============================================================
   CTA PILL (Bureau red pulse)
   ============================================================ */
.cta {
  display: inline-flex;
  align-items: center;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--ink);
  border-radius: 1.6em;
  padding: 0.85em 1.4em;
  font-family: var(--mono);
  font-size: var(--t-label);
  line-height: 1.2;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  cursor: pointer;
  align-self: flex-start;
  transition: background 0.25s ease, color 0.25s ease;
}
.cta i {
  width: 0.5em;
  height: 0.5em;
  margin-right: 0.7rem;
  position: relative;
  flex: 0 0 auto;
}
.cta i::before {
  content: '';
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: var(--red);
  animation: ctapulse1 2s linear infinite;
}
.cta i::after {
  content: '';
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
  border: 1px solid var(--red);
  border-radius: 50%;
  animation: ctapulse2 2s linear infinite;
}
.cta:hover,
.cta:focus-visible { background: var(--ink); color: var(--paper); }
.cta-onink { color: var(--paper); border-color: var(--paper); }
/* Primary: solid white pill */
.cta-solid {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--paper);
}
.cta-solid:hover,
.cta-solid:focus-visible { background: transparent; color: var(--paper); }
.cta-solid { font-weight: 700; }
.cta-note {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--t-caption);
  color: var(--mid-p);
  align-self: center;
}
.cta-onink:hover,
.cta-onink:focus-visible { background: var(--paper); color: var(--ink); }
.cta-alt {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--t-caption);
  color: var(--mid);
}

.cta-wrap { display: flex; flex-direction: column; gap: 10px; }
.card-art .cta-wrap { align-items: center; margin-top: clamp(8px, 1.4vh, 16px); text-align: center; }
.card-art .cta { align-self: center; }

/* The observers card: its art (counter + button) sizes to content —
   the fixed art height was clipping the button out of existence */
#observers .card-art {
  height: auto;
  padding-bottom: clamp(28px, 4.5vh, 52px);
}
#observers .counter-block { align-items: center; }
.claim-reply {
  display: none;
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--t-caption);
  line-height: var(--lh-body);
  color: var(--mid-p);
}
.claim-reply.show { display: block; }
/* Counter (scanline 33) */
.counter-block { display: flex; flex-direction: column; align-items: flex-start; }
#counter-cv { display: block; opacity: 0; max-width: 100%; }
/* ============================================================
   MOSAIC PLACEMENTS (desktop)
   ============================================================ */
/* ── The question card ── */
.card-question { justify-content: center; padding-bottom: clamp(44px, 7vh, 84px); }
.question-display {
  font-family: var(--mono);
  font-weight: 700;
  font-size: var(--t-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  max-width: 15em;
}
.question-display em { font-weight: 400; font-style: italic; }
.q-mark {
  color: transparent;
  -webkit-text-stroke: 2px var(--ink);
  animation: q-glitch 3.4s step-end infinite;
}

/* ── Preliminary findings: one piece, centred like a plaque ── */
.card-findings { min-height: 0; padding-bottom: clamp(60px, 8vh, 110px); }
.findings-title { font-size: var(--t-title-1); }

.clause {
  font-family: var(--serif);
  font-size: var(--t-lead);
  line-height: 1.45;
}
.clause em {
  font-style: italic;
  color: var(--mid);
}


/* ── Correspondence card ── */
.cta-line-a {
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--t-lead);
  color: var(--mid);
}
.cta-line-b {
  display: inline-block;
  font-family: var(--mono);
  font-weight: 700;
  font-size: var(--t-title-3);
  letter-spacing: -0.02em;
  border-bottom: 3px solid var(--ink);
  padding-bottom: 2px;
  margin-top: 6px;
  transition: opacity 0.2s ease;
}
.cta-line-b:hover,
.cta-line-b:focus-visible { opacity: 0.6; }
/* Anchor offsets under the sticky nav */
#work, #question, #findings, #mosaic { scroll-margin-top: calc(var(--nav-h) + var(--gap)); }

/* ============================================================
   FOOTER
   ============================================================ */
footer {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding: 30px 20px 42px;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--mid);
}
.footer-brand { font-weight: 700; color: var(--paper); }
footer { color: var(--mid-p); }

/* ============================================================
   KEYFRAMES
   ============================================================ */
@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }
@keyframes q-glitch {
  0%, 100% { -webkit-text-stroke-color: var(--ink);
             text-shadow: none; }
  12%      { -webkit-text-stroke-color: #d6006c;
             text-shadow: 3px 0 var(--plate-c); }
  18%      { -webkit-text-stroke-color: var(--ink);
             text-shadow: none; }
  47%      { -webkit-text-stroke-color: #0088b0;
             text-shadow: -3px 0 var(--plate-m); }
  54%      { -webkit-text-stroke-color: var(--ink);
             text-shadow: none; }
  76%      { -webkit-text-stroke-color: #d6006c;
             text-shadow: -2px 0 var(--plate-c), 2px 0 var(--plate-m); }
  83%      { -webkit-text-stroke-color: var(--ink);
             text-shadow: none; }
}
/* centre origin: the stem extends in BOTH directions */
@keyframes pipbob {
  0%, 100% { transform: translateX(-50%) scaleY(1);    opacity: 0.4;  }
  50%      { transform: translateX(-50%) scaleY(1.24); opacity: 0.75; }
}
@keyframes rgb-in {
  0%   { opacity: 0;
         text-shadow: 9px 0 var(--plate-m), -9px 0 var(--plate-c); }
  55%  { opacity: 1;
         text-shadow: 4px 0 var(--plate-m), -4px 0 var(--plate-c); }
  100% { opacity: 1;
         text-shadow: 0 0 rgba(214, 0, 108, 0), 0 0 rgba(0, 136, 176, 0); }
}
@keyframes g-pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.13); } }
@keyframes eye-blink { 0%, 92%, 100% { transform: scaleY(1); } 96% { transform: scaleY(0.08); } }
@keyframes spin3d {
  from { transform: rotateX(-18deg) rotateY(0deg); }
  to   { transform: rotateX(-18deg) rotateY(360deg); }
}
@keyframes moire-drift { 0%, 100% { transform: translate(-6%, -4%); } 50% { transform: translate(6%, 4%); } }
@keyframes field-drift { 0%, 100% { transform: translateX(-8px); } 50% { transform: translateX(8px); } }
@keyframes ctapulse1 {
  0%   { transform: scale(0.8); opacity: 1; }
  50%  { transform: scale(1);   opacity: 1; }
  100% { transform: scale(0.8); opacity: 1; }
}
@keyframes ctapulse2 {
  0%   { transform: scale(1);   opacity: 0.9; }
  100% { transform: scale(2.6); opacity: 0; }
}

/* ============================================================
   SKIP LINK
   ============================================================ */
.skip-link {
  position: fixed;
  top: -60px;
  left: 12px;
  z-index: 1000;
  padding: 10px 16px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: 0.8rem;
  border-radius: 4px;
  transition: top 0.2s ease;
}
.skip-link:focus-visible { top: 12px; }

/* ============================================================
   ACCESSIBILITY
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  #hero-scroll { height: calc(100svh - var(--nav-h)); }

  .hero-band [data-tile] { --tt: 1; }
  .x-pulse::before, .eye, .moire::after, .linefield::after,
  .cta i::before, .cta i::after, .scroll-pip, .wire3d, .q-mark,
  .w-face, .strip-track, .corr-disc, .strip-captions > span, .state {
    animation: none !important;
  }
  .card-findings .clause {
    opacity: 1; filter: none; text-shadow: none; animation: none;
  }
  .strip-captions span { opacity: 0; }
  .strip-captions span:first-child { opacity: 1; }
  #title-cv, #title-cv-mobile { animation: none !important; opacity: 1 !important; }
}

/* ============================================================
   MORPH TIMELINE — 21s: sphere (0–20%) → morph → cube (28–48%)
   → morph → one-sheet (56–76%) → morph home. The glitch plates
   fire only inside the morph windows.
   ============================================================ */
@keyframes wire-glitch {
  0%, 20%  { filter: none; }
  21%      { filter: drop-shadow(4px 0 rgba(214,0,108,0.75)) drop-shadow(-4px 0 rgba(0,136,176,0.75)); }
  24%      { filter: drop-shadow(-3px 0 rgba(214,0,108,0.75)) drop-shadow(3px 0 rgba(0,136,176,0.75)); }
  28%, 48% { filter: none; }
  49%      { filter: drop-shadow(4px 0 rgba(0,136,176,0.75)) drop-shadow(-4px 0 rgba(214,0,108,0.75)); }
  52%      { filter: drop-shadow(-3px 0 rgba(0,136,176,0.75)) drop-shadow(3px 0 rgba(214,0,108,0.75)); }
  56%, 76% { filter: none; }
  77%      { filter: drop-shadow(4px 0 rgba(214,0,108,0.75)) drop-shadow(-4px 0 rgba(0,136,176,0.75)); }
  80%      { filter: drop-shadow(-3px 0 rgba(0,136,176,0.75)) drop-shadow(3px 0 rgba(214,0,108,0.75)); }
  84%, 100% { filter: none; }
}

/* Each plate: great-circle ring → cube face → stacked sheet */
@keyframes morph-1 {
  0%, 20%   { border-radius: 50%; transform: rotateY(0deg); }
  28%, 48%  { border-radius: 0;   transform: translateZ(calc(var(--wd) / 2)); }
  56%, 76%  { border-radius: 0;   transform: translateZ(-6px) scaleX(0.72); }
  84%, 100% { border-radius: 50%; transform: rotateY(0deg); }
}
@keyframes morph-2 {
  0%, 20%   { border-radius: 50%; transform: rotateY(30deg); }
  28%, 48%  { border-radius: 0;   transform: rotateY(180deg) translateZ(calc(var(--wd) / 2)); }
  56%, 76%  { border-radius: 0;   transform: translateZ(-2px) scaleX(0.72); }
  84%, 100% { border-radius: 50%; transform: rotateY(30deg); }
}
@keyframes morph-3 {
  0%, 20%   { border-radius: 50%; transform: rotateY(60deg); }
  28%, 48%  { border-radius: 0;   transform: rotateY(90deg) translateZ(calc(var(--wd) / 2)); }
  56%, 76%  { border-radius: 0;   transform: translateZ(2px) scaleX(0.72); }
  84%, 100% { border-radius: 50%; transform: rotateY(60deg); }
}
@keyframes morph-4 {
  0%, 20%   { border-radius: 50%; transform: rotateY(90deg); }
  28%, 48%  { border-radius: 0;   transform: rotateY(-90deg) translateZ(calc(var(--wd) / 2)); }
  56%, 76%  { border-radius: 0;   transform: translateZ(6px) scaleX(0.72); }
  84%, 100% { border-radius: 50%; transform: rotateY(90deg); }
}
@keyframes morph-5 {
  0%, 20%   { border-radius: 50%; transform: rotateY(120deg); }
  28%, 48%  { border-radius: 0;   transform: rotateX(90deg) translateZ(calc(var(--wd) / 2)); }
  56%, 76%  { border-radius: 0;   transform: translateZ(10px) scaleX(0.72); }
  84%, 100% { border-radius: 50%; transform: rotateY(120deg); }
}
@keyframes morph-6 {
  0%, 20%   { border-radius: 50%; transform: rotateX(90deg); }
  28%, 48%  { border-radius: 0;   transform: rotateX(-90deg) translateZ(calc(var(--wd) / 2)); }
  56%, 76%  { border-radius: 0;   transform: translateZ(-10px) scaleX(0.72); }
  84%, 100% { border-radius: 50%; transform: rotateX(90deg); }
}

/* ============================================================
   THE LIVING STRIPE — fifteen specimens on a slow loop.
   Fixed square module; the track carries two copies and slides
   half its width for a seamless orbit. Hover pauses the drift;
   the specimens themselves are the interactables.
   ============================================================ */
/* The stripe is not a card: its specimens are blocks of their
   own, floating over the field, separated by the block gap */
.card-strip {
  padding: 0;
  min-height: 0;
  overflow: hidden;
  gap: 0;
  background: transparent;
}
.strip-track {
  --sqm: clamp(212px, 25vw, 325px);   /* the module */
  display: flex;
  width: max-content;
  /* 32 files x 6s a caption - drift and caption clocks share the
     period, so the loop speed stays what it was at fifteen */
  animation: strip-drift 192s linear infinite;
}
.card-strip:hover .strip-track,
.card-strip:hover .strip-captions span { animation-play-state: paused; }
.strip-item {
  width: var(--sqm);
  height: var(--sqm);
  flex: none;
  position: relative;
  overflow: hidden;
  background: var(--paper);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.35);
  /* margin (not flex gap) keeps the loop period exact:
     31 x (module + gap) = half the doubled track */
  margin-right: var(--gap);
}
.strip-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.strip-item-ink { background: oklch(5% 0.01 262); }
.strip-item-ink img { object-fit: contain; padding: 7%; box-sizing: border-box; }

/* One clean caption line floating on the field, centred */
.strip-captions {
  position: relative;
  width: 100%;   /* flex item with only absolute children computes
                    to 0 wide otherwise - the spans need a base */
  height: 1.8em;
  margin-top: var(--gap);
  padding: 0;
}
/* DIRECT children only: the caption's inner parts (.cap-t/.cap-a)
   are plain spans and must never inherit the absolute cycling
   position - a descendant selector superimposed them all at the
   centre. */
.strip-captions > span {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
  opacity: 0;
  animation: cap-cycle 192s linear infinite;
  animation-delay: calc(var(--i) * 6s);
}
@keyframes cap-cycle {
  0%      { opacity: 0; transform: translate(-50%, 6px); }
  0.29%   { opacity: 1; transform: translate(-50%, 0); }
  3.13%   { opacity: 1; transform: translate(-50%, 0); }
  3.55%   { opacity: 0; transform: translate(-50%, -6px); }
  100%    { opacity: 0; transform: translate(-50%, -6px); }
}
@keyframes strip-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (hover: hover) and (pointer: fine) {
  .strip-item { transition: filter 0.12s ease; cursor: pointer; }
  .strip-item:hover { filter: invert(1); z-index: 3; }
}

/* The fifteen specimens — each a different trick of the eye */
.x-hermann { background: var(--ink); background-image:
  repeating-linear-gradient(0deg,  transparent 0 26px, var(--paper) 26px 34px),
  repeating-linear-gradient(90deg, transparent 0 26px, var(--paper) 26px 34px); }
.x-cafe { background-color: var(--mortar); background-image:
  repeating-linear-gradient(90deg, var(--ink) 0 22px, var(--paper) 22px 44px),
  repeating-linear-gradient(90deg, var(--ink) 0 22px, var(--paper) 22px 44px),
  repeating-linear-gradient(90deg, var(--ink) 0 22px, var(--paper) 22px 44px),
  repeating-linear-gradient(90deg, var(--ink) 0 22px, var(--paper) 22px 44px);
  background-size: 100% 23%; background-repeat: no-repeat;
  background-position: 0 0, 11px 33%, 22px 66%, 11px 100%; }
.x-moire::before, .x-moire::after { content: ''; position: absolute; inset: -20%;
  background: repeating-radial-gradient(circle at 50% 50%, var(--ink) 0 4px, var(--paper) 4px 9px); }
.x-moire::after { background: repeating-radial-gradient(circle at 50% 50%, var(--ink) 0 4px, transparent 4px 9px);
  mix-blend-mode: screen; animation: moire-drift 7s ease-in-out infinite; }
.x-lines { background: repeating-linear-gradient(90deg, var(--ink) 0 3px, var(--paper) 3px 12px); }
.x-lines::after { content: ''; position: absolute; inset: -12%;
  background: repeating-linear-gradient(90deg, var(--ink) 0 3px, transparent 3px 11px);
  animation: field-drift 8s ease-in-out infinite; }
.x-checker { background: conic-gradient(var(--ink) 0 90deg, var(--paper) 90deg 180deg,
  var(--ink) 180deg 270deg, var(--paper) 270deg 360deg) 0 0 / 34px 34px; }
.x-burst { background: repeating-conic-gradient(var(--ink) 0 7.5deg, var(--paper) 7.5deg 15deg); }
.x-squares { background: repeating-radial-gradient(closest-side at 50% 50%,
  var(--ink) 0 8px, var(--paper) 8px 16px); }
.x-diag { background:
  repeating-linear-gradient(45deg,  var(--ink) 0 4px, transparent 4px 14px),
  repeating-linear-gradient(-45deg, var(--ink) 0 4px, transparent 4px 14px), var(--paper); }
.x-dots { background: radial-gradient(var(--ink) 30%, transparent 33%) 0 0 / 24px 24px var(--paper); }
.x-dots::after { content: ''; position: absolute; inset: 0;
  background: radial-gradient(var(--ink) 30%, transparent 33%) 12px 12px / 24px 24px;
  animation: moire-drift 9s ease-in-out infinite; opacity: 0.65; }
.x-zollner { background:
  repeating-linear-gradient(0deg, var(--ink) 0 5px, transparent 5px 34px),
  repeating-linear-gradient(65deg, var(--ink) 0 2px, transparent 2px 12px), var(--paper); }
.x-spiral { background: repeating-conic-gradient(from 0deg, var(--ink) 0 12deg, var(--paper) 12deg 24deg); }
.x-spiral::after { content: ''; position: absolute; inset: 18%; border-radius: 50%;
  background: repeating-conic-gradient(from 8deg, var(--ink) 0 12deg, var(--paper) 12deg 24deg); }
.x-bull { background: repeating-radial-gradient(circle at 42% 50%, var(--ink) 0 6px, var(--paper) 6px 13px); }
.x-steps { background: linear-gradient(90deg,
  #131111 0 20%, #3a3634 20% 40%, #6b6560 40% 60%, #a29b94 60% 80%, #ede8e8 80% 100%); }
.x-steps::after { content: ''; position: absolute; top: 40%; bottom: 40%; left: 6%; right: 6%;
  background: #6b6560; }
.x-weave { background:
  repeating-linear-gradient(90deg, var(--ink) 0 10px, transparent 10px 30px),
  repeating-linear-gradient(0deg,  var(--ink) 0 10px, var(--paper) 10px 30px); }
.x-pulse { background: var(--ink); display: grid; place-items: center; }
.x-pulse::before { content: ''; width: 46%; aspect-ratio: 1; border-radius: 50%;
  background: var(--paper); animation: g-pulse 3.6s ease-in-out infinite; }
.x-pulse::after { content: ''; position: absolute; width: 18%; aspect-ratio: 1;
  border-radius: 50%; background: var(--ink); }

/* ============================================================
   FINDINGS — numerals rail left, statements right; the active
   numeral lights as its statement crosses the viewport.
   ============================================================ */
.card-findings { overflow: visible; }  /* the rail must stick to the page */
.findings-layout {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) minmax(0, 3fr);
  gap: clamp(24px, 4vw, 90px);
  width: min(1100px, 100%);
  margin-top: clamp(24px, 4vh, 56px);
  text-align: left;
}
.fnd-rail {
  position: sticky;
  top: calc(var(--nav-h) + 10vh);
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 0.35em;
}
.fnd-num {
  font-family: var(--mono);
  font-weight: 700;
  font-size: var(--t-title-3);
  line-height: 1.1;
  color: var(--ink);
  opacity: 0.14;
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.fnd-num.active {
  opacity: 1;
  transform: translateX(0.3em);
  animation: num-glitch 0.5s step-end;
}
@keyframes num-glitch {
  0%   { text-shadow: 4px 0 var(--plate-m), -4px 0 var(--plate-c); }
  30%  { text-shadow: -3px 0 var(--plate-c), 3px 0 var(--plate-m); }
  65%  { text-shadow: 2px 0 var(--plate-m), -2px 0 var(--plate-c); }
  100% { text-shadow: none; }
}
.findings-doc { max-width: none; margin-top: 0; text-align: left; }
/* Only the current statement is legible: the rest dissolve into
   an unfocused RGB cloud. The observer toggles .active; the
   transitions carry the smear in and out, the keyframe snaps the
   arrival. */
.card-findings .clause {
  margin-bottom: clamp(22px, 3.6vh, 44px);
  opacity: 0.38;
  filter: blur(3px);
  text-shadow:
    6px 0 5px rgba(214, 0, 108, 0.55),
    -6px 0 5px rgba(0, 136, 176, 0.55);
  transition: opacity 0.55s ease, filter 0.55s ease, text-shadow 0.55s ease;
}
.card-findings .clause.active {
  opacity: 1;
  filter: none;
  text-shadow: 0 0 0 rgba(214, 0, 108, 0), 0 0 0 rgba(0, 136, 176, 0);
  animation: clause-glitch 0.45s step-end;
}
@keyframes clause-glitch {
  0%   { text-shadow: 4px 0 2px rgba(214, 0, 108, 0.6), -4px 0 2px rgba(0, 136, 176, 0.6); }
  40%  { text-shadow: -3px 0 1px rgba(0, 136, 176, 0.6), 3px 0 1px rgba(214, 0, 108, 0.6); }
  75%  { text-shadow: 2px 0 0 rgba(214, 0, 108, 0.5), -2px 0 0 rgba(0, 136, 176, 0.5); }
  100% { text-shadow: 0 0 0 rgba(214, 0, 108, 0), 0 0 0 rgba(0, 136, 176, 0); }
}
.card-findings .clause:last-child { margin-bottom: clamp(30px, 8vh, 90px); }

/* ============================================================
   CORRESPONDENCE — boxless, straight on the living background
   ============================================================ */
.corr {
  position: relative;
  z-index: 1;
  overflow: clip;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: clamp(90px, 16vh, 190px) 20px clamp(60px, 10vh, 130px);
}
.corr .cta-line-a { color: var(--mid-p); }
.corr .cta-line-b { color: var(--paper); border-bottom-color: var(--paper); }
/* The artwork: black spiral on its paper disc, turning as the
   piece does in exhibition */
.corr-art {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(16px, 2.6vh, 26px);
  margin-top: clamp(40px, 7vh, 90px);
}
.corr-disc {
  /* the rotation's square bbox must not create sideways scroll
     (the section clips it; the disc itself is round anyway) */
  width: min(440px, 74vw);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--paper);
  overflow: hidden;
  animation: corr-spin 45s linear infinite;
}
.corr-disc img {
  display: block;
  width: 100%;
  height: 100%;
}
.art-caption {
  font-family: var(--serif);
  font-size: var(--t-caption);
  letter-spacing: 0.02em;
  color: var(--mid-p);
}
.art-caption em { font-style: italic; }
@keyframes corr-spin { to { transform: rotate(360deg); } }
@keyframes dot-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.2; }
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width: 940px) {
  /* ── The presets retune; selectors below are layout only ── */
  :root {
    /* Phone hierarchy opens up: titles and leads step UP, the
       small voices (kickers, captions) step DOWN - the desktop
       ratios were collapsing into one cramped size */
    --t-display: clamp(2.75rem, 12vw, 6.4rem);
    --t-title-1: clamp(2.125rem, 8.5vw, 3.2rem);
    --t-title-2: clamp(1.7rem, 6.5vw, 2.6rem);
    --t-title-3: clamp(1.5rem, 4.5vw, 2rem);
    --t-lead:    clamp(1.0625rem, 4.53vw, 1.7rem);
    --t-body:    clamp(0.85rem, 2.2vw, 1.05rem);
    --t-stamp:   0.72rem;      /* the kicker holds its size */
    --t-caption: 0.72rem;
  }
  /* ── The bar is cut: brand bar, a module of air, the square ── */
  .nav {
    padding: 0;
    gap: calc(var(--gap) / 2);
    background: transparent !important;
  }
  .nav-brand {
    flex: 1;
    height: 100%;
    display: flex;
    align-items: center;
    padding: 0 16px;
    background: rgba(0, 0, 0, 0.5);
  }
  .nav-burger {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: var(--nav-h);
    height: var(--nav-h);
    flex: none;
    background: rgba(0, 0, 0, 0.5);
    border: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .nb-bar {
    display: block;
    width: 20px;
    height: 2px;
    background: var(--paper);
    transition: transform 0.32s cubic-bezier(0.2, 0, 0.2, 1),
                opacity 0.2s ease;
  }
  /* the fold: outer bars meet at the diagonal, the middle vanishes */
  .menu-open .nb-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
  .menu-open .nb-bar:nth-child(2) { opacity: 0; transform: scaleX(0.4); }
  .menu-open .nb-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

  /* the panel: drops from under the square, right-aligned */
  .nav-links {
    position: absolute;
    top: calc(100% + var(--gap));
    right: 0;
    flex-direction: column;
    align-items: flex-end;
    gap: 18px;
    padding: 22px 24px;
    background: oklch(6% 0.012 262 / 0.97);
    box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.06);
    font-size: var(--t-title-3);
    opacity: 0;
    transform: translateY(-8px);
    pointer-events: none;
    transition: opacity 0.28s ease, transform 0.28s cubic-bezier(0.2, 0, 0.2, 1);
  }
  .menu-open .nav-links {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
  /* in the panel the socials speak their names */
  .soc-label { display: inline; }

  /* Touch floor for the pills */
  .cta { min-height: 44px; padding: 0.9em 1.6em; }

  /* ── Hero: poster stacks above the text; the slot keeps a real
     rect for the dock (flexes to the space the text leaves) ── */
  .hero-band {
    flex-direction: column;
    align-items: center;
    gap: 14px;
    padding-top: calc(var(--nav-h) + var(--gap));
  }
  .hero-poster-slot {
    /* ONE geometry for ceremony and formed alike (the desktop
       principle): the slot is always the full-width sheet
       centred in the first viewport, so the travel targets the
       final spot at the final size - no leftover mini-poster,
       no jump at the collapse */
    flex: none;
    width: 100%;
    max-width: none;   /* the 52vw guard is a DESKTOP guard */
    aspect-ratio: auto;
    min-height: 0;
    height: calc((100vw - 2 * var(--gap)) / 0.72 + 34px);
    margin-top: max(0px, calc(
      (100svh - (100vw - 2 * var(--gap)) / 0.72 - 34px) / 2
      - var(--nav-h) - var(--gap)));
  }
  .hero-unit {
    flex: 0 0 auto;
    align-items: center;
    text-align: center;
    /* its own ink block, like every card below */
    background: rgba(0, 0, 0, 0.5);
    padding: 30px 20px 34px;
    margin-top: 4px;
    gap: 16px;
  }
  .hero-unit .hero-sub { max-width: 38ch; }
  .hero-ctas { justify-content: center; }
  .cta-note { flex-basis: 100%; text-align: center; }

  /* The immersive layer is unreachable from a phone: the button
     yields to a sentence in the Bureau's voice */
  #enter-immersive, .cta-note, #immersive-reply { display: none; }
  .vision-note {
    display: block;
    flex-basis: 100%;
    text-align: center;
    font-family: var(--serif);
    font-size: var(--t-body);
    color: var(--mid-p);
    max-width: 34ch;
    margin: 4px auto 0;
  }



  /* One column of cards */
  .cards { grid-template-columns: 1fr; }
  .card-half, .card-full { grid-column: 1; }
  .card { min-height: clamp(420px, 64svh, 560px); }
  /* cards whose content IS their height: the generic floor above
     was inflating them with dead space on phones */
  .card-strip { min-height: 0; }
  .card-question {
    min-height: 0;
    padding-top: clamp(44px, 9svh, 84px);
    padding-bottom: clamp(44px, 9svh, 84px);
  }
  /* captions: ALWAYS two centred lines - the split is semantic
     (work above, attribution below), so the box is always full
     and never overflows. Type a hair smaller and tighter so the
     longest title holds its single line. */
  .strip-captions { font-size: 0.68rem; height: 3.4em; }
  .strip-captions > span {
    white-space: normal;
    width: 100%;
    max-width: 100%;
    text-align: center;
    letter-spacing: 0.08em;
    line-height: 1.55;
  }
  .cap-t, .cap-a { display: block; }
  /* the findings card closes on the coda without a dead slab */
  .corr-close { margin-bottom: 0; }
  /* footer lines centred */
  footer { flex-direction: column; align-items: center; text-align: center; gap: 8px; }

  .card-split { flex-direction: column; text-align: center; }
  .card-split .card-copy { align-items: center; }


  /* Stripe module scales down with the screen */
  .strip-track { --sqm: clamp(200px, 58vw, 280px); }

  /* Question fits the narrow column */

  /* Case page */
  .page-hero { min-height: 0; }
  .page-cards { padding-top: calc(var(--nav-h) + var(--gap)); }

  /* The numerals keep their column: a tiny vertical slice on the
     left, statements compressed toward the right */
  .findings-layout { grid-template-columns: 40px minmax(0, 1fr); gap: 14px; }
  .corr-close { grid-template-columns: 40px minmax(0, 1fr); gap: 14px; }
  .fnd-num { font-size: 0.82rem; }
  .card-findings .clause { margin-bottom: clamp(40px, 8vh, 80px); }
}

/* ============================================================
   THE OBSERVER STATES — eleven title/subtitle pairs cycling with
   the misregistration glitch. 11 x 7s = 77s cycle; each state
   holds ~5.5s and hands over through the RGB split.
   ============================================================ */
.card-states { justify-content: flex-start; }
.states {
  position: relative;
  flex: 1;
  align-self: stretch;
  min-height: clamp(320px, 48svh, 520px);   /* room for display-size lines */
}
.state {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(10px, 1.5vh, 18px);
  text-align: center;
  opacity: 0;
  animation: state-cycle 77s linear infinite;
  animation-delay: calc(var(--s) * 7s);
}
.state .card-h2 {
  /* the observer statements fill the half card's box: the display
     token is the ceiling (the table's biggest voice), the fit of
     the longest statement is the driver - owner's call to keep
     the half-card pairing over the token's literal size */
  font-size: min(var(--t-display), 5.5vw, 5.75rem);
  line-height: var(--lh-display);
}
@media (max-width: 940px) {
  .state .card-h2 { font-size: min(var(--t-display), 8.5vw); }
  /* the statements keep air from the card's edge: on full-bleed
     phone cards the display type was running to the rim */
  .state { left: 4.5%; right: 4.5%; }
}
.state .card-sub { max-width: 44ch; }

@keyframes state-cycle {
  /* same absolute seconds as the 5-state version, rescaled to 77s */
  0%    { opacity: 0; text-shadow: 7px 0 var(--plate-m), -7px 0 var(--plate-c); }
  0.68% { opacity: 1; text-shadow: 4px 0 var(--plate-m), -4px 0 var(--plate-c); }
  1.36% { opacity: 1; text-shadow: -3px 0 var(--plate-c), 3px 0 var(--plate-m); }
  2.05% { opacity: 1; text-shadow: none; }
  /* hold */
  7.27% { opacity: 1; text-shadow: none; }
  /* leave through the plates */
  7.95% { opacity: 1; text-shadow: -4px 0 var(--plate-c), 4px 0 var(--plate-m); }
  8.64% { opacity: 0; text-shadow: 7px 0 var(--plate-m), -7px 0 var(--plate-c); }
  100%  { opacity: 0; text-shadow: none; }
}

@media (prefers-reduced-motion: reduce) {
  .state { animation: none; opacity: 0; }
  .state[style*="--s: 0"] { opacity: 1; }
}

/* ============================================================
   POSTER TOOLS — the instrument's own controls, at the poster's
   bottom edge. Appear with the formed poster; never part of the
   exported PNG (that image is composed programmatically).
   ============================================================ */
.poster-tools {
  position: absolute;
  left: 50%;
  bottom: 2.4%;
  transform: translateX(-50%);
  z-index: 6;
  display: flex;
  align-items: center;
  gap: 0.7em;
  opacity: var(--face-in, 0);
  transition: opacity 0.4s ease;
}
.poster-tool {
  background: none;
  border: 0;
  padding: 0.2em 0.1em;
  cursor: pointer;
  font-family: var(--mono);
  font-size: clamp(0.56rem, calc(var(--poster-w) * 0.0155), 0.68rem);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: oklch(76% 0.07 212 / 0.75);
  transition: color 0.2s ease;
}
.poster-tool:hover,
.poster-tool:focus-visible { color: var(--paper); }
.poster-tool:disabled { opacity: 0.5; cursor: wait; }
.poster-tool-sep {
  color: oklch(76% 0.07 212 / 0.4);
  font-size: 0.6rem;
}
#hero.settled .poster-tools { pointer-events: auto; }
.poster-tools { pointer-events: none; }

/* The formed state: region is one viewport, no ceremony */
body.formed-instant #hero-scroll { height: 100svh; }

/* ============================================================
   POSTER DEBUG GRID — a typographic guide fixed to the poster
   canvas, so it scales with the poster at any size. Off unless
   body.grid-on (press G, or load with ?grid). Never ships
   visible, and never reaches the exported PNG.
   ============================================================ */
.poster {
  /* poster-relative typographic frame */
  --pg-margin: 4%;        /* side margin (of poster width)   */
  --pg-top:    3.5%;      /* head margin (of poster height)  */
  --pg-foot:   3.5%;      /* foot margin                     */
  --pg-cols:   6;
  --pg-gutter: 1.6%;
  --pg-base:   2.5%;      /* baseline step (of poster height) */
}
.poster-grid {
  position: absolute;
  inset: 0;
  z-index: 9;
  pointer-events: none;
  display: none;
}
body.grid-on .poster-grid { display: block; }

/* Columns inside the margins */
.pg-cols {
  position: absolute;
  left: var(--pg-margin);
  right: var(--pg-margin);
  top: var(--pg-top);
  bottom: var(--pg-foot);
  display: grid;
  grid-template-columns: repeat(var(--pg-cols), 1fr);
  gap: var(--pg-gutter);
}
.pg-cols i { background: rgba(0, 136, 176, 0.16); }

/* Baseline rhythm */
.pg-baseline {
  position: absolute;
  left: var(--pg-margin);
  right: var(--pg-margin);
  top: var(--pg-top);
  bottom: var(--pg-foot);
  background: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.14) 0 1px,
    transparent 1px var(--pg-base));
}

/* Thirds — the classic poster reading lines */
.pg-thirds {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, transparent calc(33.333% - 1px), rgba(214, 0, 108, 0.5) calc(33.333% - 1px) 33.333%, transparent 33.333%),
    linear-gradient(to bottom, transparent calc(66.666% - 1px), rgba(214, 0, 108, 0.5) calc(66.666% - 1px) 66.666%, transparent 66.666%);
}

/* Centre axes */
.pg-axes {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to right, transparent calc(50% - 1px), rgba(214, 0, 108, 0.75) calc(50% - 1px) 50%, transparent 50%),
    linear-gradient(to bottom, transparent calc(50% - 1px), rgba(214, 0, 108, 0.35) calc(50% - 1px) 50%, transparent 50%);
}

/* Safe area: the margin box itself */
.pg-safe {
  position: absolute;
  left: var(--pg-margin);
  right: var(--pg-margin);
  top: var(--pg-top);
  bottom: var(--pg-foot);
  border: 1px solid rgba(255, 255, 255, 0.55);
}

/* Every placed element outlined while the grid is up */
body.grid-on .poster-face,
body.grid-on .hero-decl,
body.grid-on .poster-tools,
body.grid-on .spiro-wrap {
  outline: 1px dashed rgba(255, 210, 0, 0.75);
  outline-offset: 0;
}

/* Live readout */
.pg-readout {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 999;
  display: none;
  padding: 10px 12px;
  background: rgba(0, 0, 0, 0.88);
  color: #eee;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.65;
  letter-spacing: 0.02em;
  white-space: pre;
  border: 1px solid rgba(255, 255, 255, 0.18);
  pointer-events: none;
}
body.grid-on .pg-readout { display: block; }

/* ============================================================
   ONE-SHEET FOOT — corrective block (authoritative)
   presents / TITLE / release / billing / logos, tight margins.
   ============================================================ */
.poster-presents {
  top: 55.5%;
  width: 92%;
  font-size: clamp(0.5rem, calc(var(--poster-w) * 0.0135), 0.6rem);
  letter-spacing: 0.3em;
  line-height: 1.4;
  color: oklch(88% 0.02 240 / 0.85);
}
.poster-billing {
  top: 84.5%;
  width: 94%;
  font-size: clamp(0.4rem, calc(var(--poster-w) * 0.0112), 0.48rem);
  letter-spacing: 0.05em;
  line-height: 1.38;
  color: oklch(82% 0.015 240 / 0.6);
  text-transform: uppercase;
}
.poster-logos { top: 93%; width: 88%; }

/* ============================================================
   ONE-SHEET GRID LOCK (authoritative — closes the poster block)
   Every element sits on the 2.5% baseline module (M). Widths
   match the safe area (92% = 100% − 2 × 4% margin), so every
   block's edges land on the margin lines.
     presents  2M   ·  key art 4–22M  ·  title 24–30M (3M a line)
     release  31M   ·  billing 34M    ·  logos  37M
   ============================================================ */
.poster-face {
  /* the sheet speaks Operator; only the title is Sagittarius */
  font-family: var(--mono);
}
.poster-presents {
  top: 5%;                 /* 2M — above the key art */
  width: 92%;
  font-size: clamp(0.5rem, calc(var(--poster-w) * 0.0145), 0.65rem);
  font-weight: 600;
  letter-spacing: 0.3em;
  line-height: 1.4;
  color: oklch(90% 0.02 240 / 0.9);
}
.poster-release {
  top: 80%;                /* 32M — below the grown title band */
  width: 92%;
  font-weight: 600;
}
.poster-billing {
  top: 85%;                /* 34M */
  width: 92%;
  font-size: clamp(0.4rem, calc(var(--poster-w) * 0.0112), 0.48rem);
  font-weight: 400;
  letter-spacing: 0.05em;
  line-height: 1.5;        /* 0.6M a line */
  color: oklch(82% 0.015 240 / 0.62);
  text-transform: uppercase;
}
.poster-logos { top: 92.5%; width: 92%; }   /* 37M */

/* ============================================================
   FROZEN COMPOSITION (authoritative — closes the poster block)
   A poster is one artwork, not a responsive layout: every size
   here is a pure fraction of the poster itself. No rem, no
   clamp — those bind against the WINDOW and were re-paginating
   the sheet at every screen size. Scale the poster and the whole
   composition scales with it, identically, everywhere.
   ============================================================ */
.poster-presents {
  font-size: calc(var(--poster-w) * 0.0145);
  letter-spacing: 0.3em;
}
.poster-release {
  font-size: calc(var(--poster-w) * 0.027);
  letter-spacing: 0.2em;
}
.poster-billing {
  font-size: calc(var(--poster-w) * 0.0105);
  letter-spacing: 0.04em;
  line-height: 1.45;
}
/* (tools left the sheet - see the instrument strip below) */
.poster-logos img { height: calc(var(--poster-h) * 0.024); }


/* ============================================================
   INSTRUMENT BAR - a small black bar docked under the sheet
   (the sheet cedes 34px of height for it). Page furniture in
   deliberate contrast with the poster's voice: lowercase, small,
   fixed UI size. Never part of the artwork or the export.
   ============================================================ */
.poster { transform: translateY(calc(var(--p) * -17px)); }
.poster-tools {
  position: absolute;
  left: 50%;
  top: calc(50% + var(--poster-h) / 2 - 17px);
  bottom: auto;
  transform: translateX(-50%);
  width: var(--poster-w);
  height: 34px;
  background: oklch(5% 0.01 262);
  box-shadow: inset 0 0 0 1px oklch(100% 0 0 / 0.06);
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  opacity: var(--face-in);
}
.poster-tool {
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: oklch(62% 0.01 240);
  background: none;
  border: 0;
  padding: 2px 4px;
  cursor: pointer;
}
.poster-tool:hover { color: oklch(85% 0.02 240); }
.poster-tool-sep { font-size: 11px; color: oklch(40% 0.01 240); }


/* ============================================================
   THE PLATE - the composed one-sheet bitmap. Sits over the live
   layers; once painted, the DOM poster pieces hide beneath it.
   The debug grid stays above (it inspects the plate too).
   ============================================================ */
.poster-plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 6;
  /* development is DIGITAL now: the image assembles in scanline
     rows with RGB registration, drawn by js/poster-compose.js -
     no photographic fade */
  opacity: 1;
}
/* the bath itself: covers the live (or outgoing) poster the
   moment a plate is on its way - near-black with a faint
   red-light breath, the print developing under safelight */
.poster::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 5;
  /* developer under safelight: a warm translucent wash that DIMS
     the paper - photographic paper never turns black in the bath */
  background:
    radial-gradient(120% 90% at 50% 40%, oklch(30% 0.11 25 / 0.3), transparent 75%),
    oklch(12% 0.04 25 / 0.55);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.45s ease;
}
body.developing .poster::after {
  opacity: 1;
  animation: bath-breathe 1.6s ease-in-out infinite;
}
@keyframes bath-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.86; }
}
/* the outgoing print sinking into the bath before its successor */
@keyframes plate-sink {
  to { opacity: 0; filter: brightness(0.05) blur(3px); }
}
body.plated .spiro-wrap,
body.plated .hero-dot,
body.plated .hero-decl,
body.plated .poster-face,
body.plated .poster::before { visibility: hidden; }
.poster-grid { z-index: 8; }

/* On a formed-state reload the poster IS the plate: the live
   layers never flash their unprocessed rendering while the
   compositor works - the sheet holds dark for a beat and the
   finished plate fades in. (The ceremony path is untouched:
   without formed-instant everything stays live and vectorial.) */
body.formed-instant:not(.plated) .spiro-wrap,
body.formed-instant:not(.plated) .hero-dot,
body.formed-instant:not(.plated) .hero-decl,
body.formed-instant:not(.plated) .poster-face { visibility: hidden; }


/* The DOM faces are layout reference only (grid debugging, zone
   measurement): the plate is the poster's sole renderer, and the
   faces must never paint - not even under the bath mid-close. */
.poster-face { visibility: hidden; }


/* The plate owns the title. The live scanline canvases are pure
   machinery now (they publish the raster the compositor and the
   scrub math read) and must never paint: the opening is textless
   by design, the close shows only the flower riding into the
   sheet, and the title arrives fully formed on the developed
   print. (This also matters for stacking: .hero-decl sits at
   z-index 10, ABOVE the bath - any nonzero opacity puts a
   half-formed title over the darkroom.) */
.hero-decl { opacity: 0; }


/* Caption under the 33 counter - it belongs to the number */
.counter-caption {
  display: block;
  margin-top: 0;
  font-family: var(--mono);
  font-size: var(--t-label);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: center;
  color: currentColor;
  opacity: 0.65;
}


/* Observer Access: the second thought steps back a size */
.card-sub-quiet {
  font-size: var(--t-body);
  margin-top: 14px;
}

/* The counter reads as one instrument: number, caption, control */
#observers .counter-caption { margin-top: 0; }
#observers .card-art .cta-wrap { margin-top: 20px; }

/* Correspondence: the archive's coda - past the numbered findings,
   aligned on the statements column (the grid mirrors
   .findings-layout exactly), arriving like a statement but with a
   longer, deeper rack-focus and a harder glitch. */
.corr-close {
  display: grid;
  grid-template-columns: minmax(120px, 1fr) minmax(0, 3fr);
  gap: clamp(24px, 4vw, 90px);
  width: min(1100px, 100%);
  margin: clamp(120px, 24vh, 280px) 0 clamp(20px, 5vh, 60px);
  font-family: var(--serif);
  font-size: var(--t-lead);
  line-height: 1.45;
  text-align: left;
  transition:
    opacity 1.5s cubic-bezier(0.22, 1, 0.36, 1),
    filter 1.5s cubic-bezier(0.22, 1, 0.36, 1),
    text-shadow 1.5s cubic-bezier(0.22, 1, 0.36, 1),
    transform 1.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.corr-close-body { grid-column: 2; }
.corr-close em { font-style: italic; color: var(--mid); }
.corr-close a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
/* pending: deep in the RGB cloud, low and unfocused */
.corr-close.cine-pending {
  opacity: 0.1;
  filter: blur(10px);
  transform: translateY(22px);
  text-shadow:
    16px 0 12px rgba(214, 0, 108, 0.5),
    -16px 0 12px rgba(0, 136, 176, 0.5);
}
/* arrival: the transition racks focus; the glitch snaps it home */
.corr-close.cine-in {
  animation: corr-glitch 0.9s step-end 0.5s;
}
@keyframes corr-glitch {
  0%   { text-shadow: 7px 0 3px rgba(214, 0, 108, 0.65), -7px 0 3px rgba(0, 136, 176, 0.65); }
  28%  { text-shadow: -5px 0 2px rgba(0, 136, 176, 0.65), 5px 0 2px rgba(214, 0, 108, 0.65); }
  55%  { text-shadow: 3px 0 1px rgba(214, 0, 108, 0.55), -3px 0 1px rgba(0, 136, 176, 0.55); }
  80%  { text-shadow: -2px 0 0 rgba(0, 136, 176, 0.45), 2px 0 0 rgba(214, 0, 108, 0.45); }
  100% { text-shadow: 0 0 0 rgba(214, 0, 108, 0), 0 0 0 rgba(0, 136, 176, 0); }
}


/* Personnel: section labels between credit groups */
.credits-sec {
  display: block;
  margin-top: clamp(22px, 3.5vh, 40px);
  font-family: var(--mono);
  font-size: var(--t-label);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--mid-p);
}


/* Closing notes ('The case leaves room...', 'Best described as a
   controlled exposure.'): their own beat - separated by space,
   not by size */
.card-note {
  margin-top: 10px;
  font-size: var(--t-body);
}

/* 'Not classified as a film / as a game': each sentence holds one
   line - the size yields to the card, never the line to the size */
.card-h2-oneline {
  font-size: min(var(--t-title-2), 3.1vw);
  white-space: nowrap;
}
@media (max-width: 940px) {
  .card-h2-oneline { font-size: min(var(--t-title-2), 6vw); }
}

/* Observer states: the running sentences grow with the titles */
.state .card-sub { font-size: var(--t-title-2); line-height: 1.25; }


/* Provenance: a lone last entry spans and centres */
.meta-credits .credit-center { grid-column: 1 / -1; }

/* Special thanks: names only, centred, in the record's voice */
.credits-thanks {
  margin-top: 10px;
  text-align: center;
  font-size: var(--t-body);
  font-weight: 600;
}


/* ============================================================
   MOBILE FORMED HERO (after every formed-instant base rule, so
   the cascade cannot undo it): the poster takes the full content
   width - the hero grows past one viewport and the page simply
   scrolls. Ceremony geometry untouched.
   ============================================================ */
@media (max-width: 940px) {
  /* The hand-off happens in the dark: while the print develops
     (and the hero reflows to the full-width poster) a black
     cover holds the whole hero; it lifts as the plate paints.
     The copy waits for the plate too - so the small mid-scrub
     poster with text beneath can never flash. */
  #hero::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 10;
    /* the whole room goes dark under the safelight while the
       develop begins (and the hero reflows beneath it) - warm,
       never black, and only during the chemistry */
    background:
      radial-gradient(120% 90% at 50% 40%, oklch(26% 0.1 25 / 0.35), transparent 75%),
      oklch(8% 0.03 25 / 0.96);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
  }
  body.developing:not(.plated) #hero::after { opacity: 1; }
  body:not(.plated) .hero-band .hero-unit { visibility: hidden; }

  body.formed-instant #hero-scroll { height: auto; }
  body.formed-instant #hero-scroll #hero { height: auto; position: relative; }
  body.formed-instant .hero-band { position: relative; inset: auto; }
  body.formed-instant .hero-poster-slot {
    flex: none;
    height: calc((100vw - 2 * var(--gap)) / 0.72 + 34px);
    /* the sheet centres in the first viewport, so the ceremony
       flower barely moves to find its spot - and the formed
       opening reads like a cover */
    margin-top: max(0px, calc(
      (100svh - (100vw - 2 * var(--gap)) / 0.72 - 34px) / 2
      - var(--nav-h) - var(--gap)));
  }
  /* the presents line breathes on two lines */
  .m-br { display: inline; }

  /* the nav runs edge to edge; the poster keeps its margins on
     the ink - a sheet lying on the block, not becoming it */
  .nav { left: 0; right: 0; }

  /* ── The opening is ONE block: the band carries the ink surface,
     poster above, declaration below - and the 33 stays on the
     case page (the homepage path to it is 'Learn more') ── */
  body.formed-instant .hero-band { background: rgba(0, 0, 0, 0.5); }
  body.formed-instant .hero-unit { background: transparent; margin-top: 0; }
  .home #observers { display: none; }

  /* Blocks run edge to edge: no side margins on phones - only
     the vertical rhythm between them remains */
  .cards { padding-left: 0; padding-right: 0; }

  /* Learn more is the door: make it unmistakable */
  .hero-ctas .cta-solid {
    font-size: 0.82rem;
    min-height: 52px;
    padding: 1.1em 2.6em;
  }

  /* The instrument bar waits for a touch of the poster */
  body.formed-instant .poster { pointer-events: auto; cursor: pointer; }
  .poster-tools {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
  }
  body.tools-open .poster-tools {
    opacity: 1;
    pointer-events: auto;
  }
}


/* The relics cabinet: the historical block's wireframe objects */
#relics-cv {
  display: block;
  width: min(100%, 440px);
  aspect-ratio: 1;
  margin: 0 auto;
}

/* ============================================================
   DESKTOP INSTRUMENTS - no black bar: 'draw another · download'
   float on the living background beneath the sheet, in the same
   voice as the caption under the archive stripe. (Phones keep
   the tap-summoned bar on the ink block.)
   ============================================================ */
@media (min-width: 941px) {
  .poster-tools {
    background: transparent;
    box-shadow: none;
    gap: 16px;
    padding-top: 14px;
  }
  .poster-tool {
    font-size: var(--t-label);
    font-weight: 600;
    letter-spacing: var(--ls-label);
    color: var(--paper);
    opacity: 0.72;
    transition: opacity 0.2s ease;
  }
  .poster-tool:hover { color: var(--paper); opacity: 1; }
  .poster-tool-sep { color: oklch(70% 0.01 240 / 0.5); }
}


/* ============================================================
   THE CINEMATIC OPENING - no interaction, no interface. The page
   is locked and bare until the poster develops; then the nav and
   the declaration arrive together.
   ============================================================ */
body.opening { overflow: hidden; }
body.opening .nav {
  opacity: 0;
  pointer-events: none;
}
.nav { transition: opacity 0.6s ease 0.2s; }
body.opening .scroll-pip { display: none; }

/* the copy waits for the plate on every screen (was mobile-only),
   then rises in as the print resolves */
body:not(.plated) .hero-band .hero-unit { visibility: hidden; }
body.plated .hero-band .hero-unit {
  animation: unit-in 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.35s both;
}
@keyframes unit-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}


/* The record line: its own quiet beat under the declaration */
.hero-record {
  margin-top: 6px;
  font-family: var(--serif);
  font-size: var(--t-body);
  color: var(--mid-p);
}
.hero-record em { font-style: italic; }


/* TikTok waits: the mark is present but not yet a door */
.soc-soon {
  opacity: 0.38;
  pointer-events: none;
  cursor: default;
}
