/* THE LONG EXPOSURE — whereishairglasses.com
   ═══════════════════════════════════════════════════════════════════
   SAFELIGHT — v2 design system

   The site is an undeveloped darkroom. Every surface is either
   photographic material or a darkroom instrument.

     EMULSION   the operator. organic, silver-grain, safelight-lit.
                a latent image that only develops when someone decides to.
                face: Fraunces (display) + Newsreader (his journal prose).
     INSTRUMENT the fleet. enlarger timer, densitometer, hairline rules,
                tabular numerals. it runs itself and measures everything.
                face: IBM Plex Mono. chrome, terminal, tables, counters.

   Two rationed accents, on true black:
     --safelight  warm amber   a human is present
     --developer  cold cyan    the system is present
   They never share a block. The one sanctioned seam is the ledger.
   Instruments run themselves; an emulsion becomes an image only when a
   person decides to develop it. The visitor's vote is the fix bath.

   All type self-hosted, OFL. No external origin but the page itself.
   Layers: tokens → base → primitives → pages → modes.
   ═══════════════════════════════════════════════════════════════════ */

@layer tokens, base, primitives, pages, modes;

/* ── self-hosted faces (OFL; see docs/ASSETS-AND-LICENSING.md) ────── */
@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("/assets/fonts/fraunces-var.woff2") format("woff2");
}
@font-face {
  font-family: "Fraunces";
  font-style: italic;
  font-weight: 300 700;
  font-display: swap;
  src: url("/assets/fonts/fraunces-var-italic.woff2") format("woff2");
}
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/newsreader.woff2") format("woff2");
}
@font-face {
  font-family: "Newsreader";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/newsreader-italic.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/plexmono-400.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Mono";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/plexmono-600.woff2") format("woff2");
}
@font-face {
  font-family: "Plex Mono";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/plexmono-400-italic.woff2") format("woff2");
}

/* Metric-matched fallbacks: hold the same box as the real face so a swap
   does not reflow. Numbers derived from each font's own hhea/OS-2 metrics
   against its fallback (see bin/stamp-fonts notes). */
@font-face {
  font-family: "Fraunces Fallback";
  src: local("Georgia"), local("Times New Roman");
  ascent-override: 97.8%;
  descent-override: 25.5%;
  line-gap-override: 0%;
  size-adjust: 102%;
}
@font-face {
  font-family: "Newsreader Fallback";
  src: local("Georgia"), local("Times New Roman");
  ascent-override: 73.5%;
  descent-override: 26.5%;
  line-gap-override: 0%;
  size-adjust: 106%;
}
@font-face {
  font-family: "Plex Fallback";
  src: local("Menlo"), local("Consolas");
  ascent-override: 102.5%;
  descent-override: 27.5%;
  line-gap-override: 0%;
  size-adjust: 95%;
}

/* Registered custom properties so motion/gradient tokens can animate. */
@property --sweep { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
@property --leak { syntax: "<percentage>"; inherits: false; initial-value: 0%; }
/* dust drifting in the safelight: only the mote background layer moves, so the
   leak gradients stay anchored top-left while these offsets animate. */
@property --mote-x { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --mote-y { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --leak-x { syntax: "<percentage>"; inherits: false; initial-value: 20%; }

@layer tokens {
  :root {
    /* ── ground: undeveloped emulsion on true black ── */
    --void: #000000;
    --surface: #0a0a0a;
    --surface-2: #121212;
    --line: #3a3a3a;
    --line-strong: #6b6b6b;
    --muted: #8f8f8f;      /* metadata; 5.3:1 on void */
    --fg: #c6c6c6;
    --bright: #ececec;

    /* ── the human is present: safelight amber ── */
    --safelight: #d08a45;
    --safelight-dim: #7a4f26;
    --safelight-bright: #f0b46a;
    --safelight-veil: color-mix(in oklab, var(--safelight) 15%, transparent);

    /* ── the system is present: developer cyan ── */
    --developer: #7a9e9e;
    --developer-dim: #3f5252;
    --developer-bright: #a8c8c8;
    --developer-veil: color-mix(in oklab, var(--developer) 10%, transparent);

    /* contact-sheet unselected-cell tints (ledger.js reads these values) */
    --safelight-ghost: #2a1c0e;
    --developer-ghost: #16201f;

    --psych: #a34d7a;      /* wordmark gradient stop only */

    /* interaction is neutral: neither voice owns focus/hover */
    --focus: var(--bright);

    /* input placeholder: present as absence, not as content */
    --placeholder: #4a4a4a;

    /* Houdini emulsion grain painted into plate sleeves (Chromium only;
       resolves to an invalid, ignored value elsewhere). plain mode -> none. */
    --sleeve-paint: paint(emulsion);

    /* ── type: three instruments, one job each ── */
    --display: "Fraunces", "Fraunces Fallback", Georgia, serif;
    --serif: "Newsreader", "Newsreader Fallback", Georgia, serif;
    --mono: "Plex Mono", "Plex Fallback", ui-monospace, Menlo, Consolas, monospace;

    /* ── modular type scale, clamp-as-floor. text ratio 1.2 ── */
    --step--1: clamp(0.78rem, 0.75rem + 0.15vw, 0.84rem);
    --step-0:  clamp(0.94rem, 0.90rem + 0.20vw, 1.00rem);
    --step-1:  clamp(1.06rem, 1.00rem + 0.30vw, 1.20rem);
    --step-2:  clamp(1.24rem, 1.14rem + 0.50vw, 1.50rem);
    --step-3:  clamp(1.50rem, 1.32rem + 0.90vw, 2.05rem);
    --step-4:  clamp(1.90rem, 1.55rem + 1.70vw, 3.10rem);
    --step-5:  clamp(2.40rem, 1.70rem + 3.40vw, 5.00rem);
    --step-6:  clamp(3.00rem, 1.60rem + 6.90vw, 8.20rem);

    /* ── spacing rhythm, 4px base ── */
    --s1: 0.25rem; --s2: 0.5rem;  --s3: 0.75rem; --s4: 1rem;
    --s5: 1.5rem;  --s6: 2.5rem;  --s7: 4rem;    --s8: 6.5rem;

    --measure: 66ch;
    --measure-tight: 58ch;
    --gut: clamp(1.15rem, 5vw, 3rem);
    --hair: 1px;

    /* ── motion: instruments settle, images develop. nothing bounces ── */
    --dur-tick: 120ms;
    --dur-settle: 420ms;
    --dur-develop: 1100ms;
    --stagger: 90ms;
    --ease-settle: linear(0, 0.55 32%, 0.84 58%, 0.96 78%, 1);
    --ease-develop: cubic-bezier(0.19, 1, 0.22, 1);
  }
}

@layer base {
  *, *::before, *::after { box-sizing: border-box; }

  html {
    background: var(--void);
    color-scheme: dark;
    scrollbar-color: var(--line) var(--void);
    -webkit-text-size-adjust: 100%;
  }

  body {
    margin: 0;
    background: var(--void);
    color: var(--fg);
    font-family: var(--mono);
    font-size: var(--step-0);
    line-height: 1.7;
    letter-spacing: 0.004em;
    padding: var(--gut);
    min-height: 100svh;
    overflow-x: hidden;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    font-variant-numeric: tabular-nums;
  }

  /* isolate so main::before (film damage, z:-1) paints over the body ground
     but still behind the text, instead of being occluded by the body bg. */
  main { max-width: var(--measure); margin: 0 auto; isolation: isolate; }

  /* ── the safelight leak: the one ambient light source. top-left,
     as if a darkroom door is ajar. static, painted behind everything. ── */
  body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -2;
    pointer-events: none;
    /* three layers: the safelight leak (breathes via --leak), the developer
       counter-leak, and two fine dust-mote grids that drift by --mote-x/y.
       Only the mote layers carry a background-position, so the leaks stay put. */
    background:
      radial-gradient(90vmax 70vmax at 8% 0%,
        var(--safelight-veil), transparent calc(58% + var(--leak))),
      radial-gradient(80vmax 80vmax at 108% 112%,
        var(--developer-veil), transparent 62%),
      radial-gradient(circle 1.1px at 50% 50%, var(--safelight-veil), transparent 62%),
      radial-gradient(circle 0.9px at 50% 50%, var(--developer-veil), transparent 62%);
    background-size: auto, auto, 132px 132px, 208px 208px;
    background-repeat: no-repeat, no-repeat, repeat, repeat;
    background-position:
      0 0, 0 0,
      var(--mote-x) var(--mote-y),
      calc(var(--mote-x) * -0.6) calc(var(--mote-y) * 0.8);
    animation:
      leak-breathe 17s var(--ease-settle) infinite,
      motes 64s linear infinite;
  }
  /* the safelight tube warming and dimming, and the dust turning over slowly */
  @keyframes leak-breathe { 0%, 100% { --leak: 0%; } 50% { --leak: 7%; } }
  @keyframes motes {
    from { --mote-x: 0px; --mote-y: 0px; }
    to   { --mote-x: 132px; --mote-y: -176px; }
  }

  /* ── film grain: real blue-noise tile, static, screen-blended. black
     stays black on OLED; grain lives only in the greys. ── */
  body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image: url("/assets/img/grain.png");
    background-size: 160px 160px;
    mix-blend-mode: soft-light;
    opacity: 0.42;
    /* living film grain: the emulsion breathes and the grain crawls, so the
       page never sits perfectly still. slow, so it reads as texture, not noise. */
    animation: grain-alive 9s ease-in-out infinite;
  }
  @keyframes grain-alive {
    0%   { opacity: 0.40; background-position: 0 0; }
    50%  { opacity: 0.50; background-position: 26px -18px; }
    100% { opacity: 0.40; background-position: 0 0; }
  }

  /* ── the emulsion fog field (field.js, data-field="fog"): a full-bleed
     WebGL2 haze on the two deadest static pages. Fixed and pointer-transparent
     and aria-hidden, so it never blocks text or interaction. z-index:0 makes
     it a positioned box that paints over the opaque body ground (unlike the
     negative-z leak/grain), while the mount is placed BEFORE <main> in the
     DOM, so main's isolated stacking context paints its text ABOVE the fog.
     The canvas is alpha-blended, so the safelight leak still reads through it.
     Reduced motion draws one still frame; plain mode and no-WebGL2 never
     paint here at all (field.js simply does not mount). No colour ships from
     this rule -- the field's tint comes from the CSS tokens via a uniform. ── */
  .field-bg {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
  }
  .field-bg canvas { display: block; width: 100%; height: 100%; }

  /* ══ v4 instrument frame + film damage — site-wide, zero markup ══
     Every effect below reaches all 25 pages from this one stylesheet. Colour
     is a CSS var throughout (mask defines shape, --token defines colour), so
     nothing here is a palette literal. The global reduced-motion rule freezes
     each animation to its still frame; plain mode strips them (see @layer
     modes). "meander" and "reticle" are both in the brand vocabulary. */

  /* the meander: a Greek-key band framing the top and bottom edges. The key is
     a mask, so its colour follows --line-strong and no hex ships in the URI. */
  html::before, html::after {
    content: ""; position: fixed; left: 0; right: 0; height: 14px;
    /* positive z: html's own pseudo-elements paint behind the opaque body
       background, so the frame must sit above content (pointer-events none). */
    z-index: 3; pointer-events: none; opacity: 0.55;
    background-color: var(--line-strong);
    --meander: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2018'%3E%3Cpath%20d='M0%2015%20H24%20M4%2015%20V4%20H20%20V12%20H9%20V8%20H14'%20fill='none'%20stroke='white'%20stroke-width='2'/%3E%3C/svg%3E");
    -webkit-mask: var(--meander) repeat-x; mask: var(--meander) repeat-x;
    -webkit-mask-size: 24px 14px; mask-size: 24px 14px;
  }
  html::before { top: 0; animation: meander-drift 40s linear infinite; }
  html::after  { bottom: 0; transform: scaleY(-1);
                 animation: meander-drift-rev 52s linear infinite; }
  @keyframes meander-drift {
    from { -webkit-mask-position: 0 0; mask-position: 0 0; }
    to   { -webkit-mask-position: 24px 0; mask-position: 24px 0; }
  }
  @keyframes meander-drift-rev {
    from { -webkit-mask-position: 0 0; mask-position: 0 0; }
    to   { -webkit-mask-position: -24px 0; mask-position: -24px 0; }
  }

  /* film damage: thin vertical scratches drifting across the print, and a slow
     safelight leak sweeping the frame. reads as a physical print under a lamp. */
  main::before {
    content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background:
      /* the safelight torch: a warm pool tracking the pointer (torch.js sets
         --torch-x/y; default off-screen so there is no glow until it moves) */
      radial-gradient(circle 380px at var(--torch-x, -500px) var(--torch-y, -500px),
        var(--safelight-dim) 0%, var(--safelight-veil) 42%, transparent 70%),
      repeating-linear-gradient(90deg, transparent 0 136px,
        var(--safelight-dim) 136px 136.8px, transparent 137.6px 314px),
      repeating-linear-gradient(90deg, transparent 0 222px,
        var(--line-strong) 222px 222.7px, transparent 223.4px 511px),
      radial-gradient(38vmax 62vmax at var(--leak-x) -12%,
        var(--safelight-veil), transparent 68%);
    background-size: auto, 314px 100%, 511px 100%, auto;
    background-repeat: no-repeat, repeat-x, repeat-x, no-repeat;
    mix-blend-mode: screen;
    animation: scratch-drift 7s steps(1) infinite,
               leak-sweep 23s ease-in-out infinite;
  }
  /* the torch layer (first) holds still at 0 0; only the two scratch layers
     drift. element opacity flickers like an unsteady lamp. */
  @keyframes scratch-drift {
    0%   { background-position: 0 0, 0 0, 0 0, 0 0; opacity: 0.34; }
    20%  { background-position: 0 0, 40px 0, -60px 0, 0 0; opacity: 0.58; }
    40%  { background-position: 0 0, 92px 0, -22px 0, 0 0; opacity: 0.30; }
    55%  { background-position: 0 0, 61px 0, -124px 0, 0 0; opacity: 0.64; }
    80%  { background-position: 0 0, 150px 0, -80px 0, 0 0; opacity: 0.38; }
    100% { background-position: 0 0, 205px 0, -166px 0, 0 0; opacity: 0.34; }
  }
  @keyframes leak-sweep { 0%, 100% { --leak-x: 10%; } 50% { --leak-x: 86%; } }

  /* a darkroom registration reticle, slowly aligning, in the bottom-right
     corner. one rationed developer-cyan accent: the system finding its mark. */
  .sysfoot::before {
    content: ""; position: fixed; right: 15px; bottom: 15px;
    /* positive z (corner overlay mark): same body-bg occlusion as the frame. */
    width: 34px; height: 34px; z-index: 2; pointer-events: none; opacity: 0.5;
    background-color: var(--developer-dim);
    --reticle: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2040%2040'%3E%3Ccircle%20cx='20'%20cy='20'%20r='14'%20fill='none'%20stroke='white'%20stroke-width='1.4'/%3E%3Cpath%20d='M20%202%20V12%20M20%2028%20V38%20M2%2020%20H12%20M28%2020%20H38%20M20%2015%20V25%20M15%2020%20H25'%20fill='none'%20stroke='white'%20stroke-width='1.4'/%3E%3C/svg%3E");
    -webkit-mask: var(--reticle) center/contain no-repeat;
    mask: var(--reticle) center/contain no-repeat;
    animation: reticle-spin 34s linear infinite;
  }
  @keyframes reticle-spin { to { transform: rotate(360deg); } }

  /* headings breathe on the optical-size axis (Fraunces is the only variable
     face; Newsreader and Plex Mono are static). the display type stays alive. */
  h1:not(.wordmark), h2 { animation: opsz-breathe 15s ease-in-out infinite; }
  @keyframes opsz-breathe {
    0%, 100% { font-variation-settings: "opsz" 80; }
    50%      { font-variation-settings: "opsz" 144; }
  }

  /* the page develops as you read it: block content resolves out of blur as it
     scrolls into view. enhance-only — @supports keeps it off where scroll
     timelines are unsupported, and it is disabled entirely under reduced
     motion, so content is always fully present without it. */
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {
      h2, figure, .panel, .artifact, .machine-block, .operator-block,
      .index, blockquote, .gate, .record, .cascade {
        animation: develop-reveal linear both;
        animation-timeline: view();
        animation-range: entry 2% entry 36%;
      }
      @keyframes develop-reveal {
        from { opacity: 0.1; filter: blur(6px); transform: translateY(20px); }
        to   { opacity: 1; filter: blur(0); transform: none; }
      }
    }
  }

  a { color: var(--fg); text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
  a:hover { color: var(--bright); }
  a:focus-visible, button:focus-visible, input:focus-visible, [tabindex]:focus-visible {
    outline: 2px solid var(--focus);
    outline-offset: 2px;
  }

  ::selection { background: var(--bright); color: var(--void); }
  ::placeholder { color: var(--placeholder); opacity: 1; }

  img { max-width: 100%; height: auto; }

  .skip {
    position: absolute; left: -9999px; top: 0;
    background: var(--surface); color: var(--bright);
    padding: var(--s2) var(--s4); border: var(--hair) solid var(--line-strong);
  }
  .skip:focus { left: var(--s4); top: var(--s4); z-index: 10; }

  /* screen-reader-only, never display:none (AT must still read it) */
  .sr, .visually-hidden {
    position: absolute !important;
    width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
}

@layer primitives {
  /* ── .rule: the hairline, the site's fundamental mark ── */
  .rule { border: 0; border-top: var(--hair) solid var(--line); margin: var(--s6) 0; }
  .rule--strong { border-top-color: var(--line-strong); }

  /* ── .panel: a plate sleeve. double hairline + corner registration
     ticks. never a card, never a shadow, never a radius. ── */
  .panel {
    position: relative;
    background: var(--surface);
    border: var(--hair) solid var(--line);
    padding: var(--s5);
    box-shadow: inset 0 0 0 3px var(--void), inset 0 0 0 4px var(--line);
  }
  .panel::before, .panel::after {
    content: "";
    position: absolute;
    width: 10px; height: 10px;
    border: var(--hair) solid var(--line-strong);
  }
  .panel::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
  .panel::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }

  /* ── .btn: an instrument control. bracketed, mono, precise ── */
  .btn, button.chrome, .prompt button, .gate-help-button {
    font-family: var(--mono);
    font-size: var(--step--1);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--fg);
    background: transparent;
    border: var(--hair) solid var(--line-strong);
    padding: var(--s2) var(--s4);
    cursor: pointer;
    transition: color var(--dur-tick) var(--ease-settle),
                border-color var(--dur-tick) var(--ease-settle),
                background var(--dur-tick) var(--ease-settle);
  }
  .btn:hover, button.chrome:hover, .prompt button:hover, .gate-help-button:hover {
    color: var(--bright); border-color: var(--bright); background: var(--surface);
  }
  .btn[disabled], button[disabled] { color: var(--muted); border-color: var(--line); cursor: default; }

  /* ── .field: an input on the instrument. underruled, not boxed ── */
  .field, .prompt input, input[type="text"] {
    font-family: var(--mono);
    font-size: var(--step-0);
    color: var(--bright);
    background: transparent;
    border: 0;
    border-bottom: var(--hair) solid var(--line-strong);
    padding: var(--s2) 0;
    caret-color: var(--developer-bright);
  }
  .field:focus, .prompt input:focus { outline: 0; border-bottom-color: var(--focus); }
}

@layer pages {
  /* ── chrome type: the instrument voice ── */
  .chrome, .statusbar, .logmeta, .gate-label, .hint, .sysfoot {
    font-family: var(--mono);
    font-size: var(--step--1);
    letter-spacing: 0.09em;
    color: var(--muted);
    text-transform: uppercase;
  }

  /* ── the instrument bar: a densitometer strip across the top ── */
  .statusbar {
    position: relative;
    display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
    justify-content: space-between; align-items: baseline;
    padding-bottom: var(--s3);
    margin: 0 0 var(--s6);
    border-bottom: var(--hair) solid var(--line);
  }
  .statusbar b { color: var(--developer); font-weight: 600; font-variant-numeric: tabular-nums; }
  /* a densitometer trace reads across the top rule, left to right, forever */
  .statusbar::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, var(--developer) 50%, transparent);
    background-size: 30% 100%; background-repeat: no-repeat;
    animation: densito 7s var(--ease-settle) infinite;
  }
  @keyframes densito {
    from { background-position: -30% 0; }
    to   { background-position: 130% 0; }
  }

  /* ── the boot terminal: the fleet speaking. CRT is diegetic here,
     and only here — scanlines live inside this viewport. ── */
  #terminal {
    position: relative;
    min-height: 46svh;
    white-space: pre-wrap;
    color: var(--developer-bright);
    font-size: var(--step-0);
    line-height: 1.85;
  }
  #terminal::after {
    content: "";
    position: absolute; inset: 0;
    pointer-events: none;
    background: repeating-linear-gradient(
      to bottom, transparent 0 2px, rgba(0,0,0,0.28) 2px 3px);
    mix-blend-mode: multiply;
  }
  #terminal .cursor {
    display: inline-block; width: 0.6ch; height: 1.05em;
    background: var(--developer-bright); vertical-align: -0.15em;
    animation: blink 1.05s steps(1) infinite;
  }
  @keyframes blink { 50% { opacity: 0; } }

  /* ── machine-block: the fleet's measured prose ── */
  .machine-block { color: var(--fg); }
  .machine-block p { max-width: var(--measure); margin: 0 0 var(--s4); }

  /* ── the wordmark: Fraunces, the emulsion resolving into a name.
     amber → developer crossover is the one sanctioned two-accent object,
     and it is the payoff of the boot. ── */
  .wordmark {
    font-family: var(--display);
    font-weight: 600;
    font-optical-sizing: auto;
    font-size: var(--step-5);
    line-height: 0.95;
    letter-spacing: -0.01em;
    margin: 0;
    display: flex; flex-direction: column;
    text-wrap: balance;
  }
  .wordmark .w1 { font-style: italic; font-weight: 400; color: var(--safelight); }
  .wordmark .w2 {
    color: var(--developer);
    background: linear-gradient(100deg,
      var(--safelight) 0%, var(--psych) calc(40% + var(--sweep)), var(--developer) 80%);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
    /* develop the crossover in once, then let the safelight breathe across
       the letters forever -- a slow warm drift of the amber/developer seam,
       reusing the same --sweep property so no colour literal is introduced. */
    animation: sweep var(--dur-develop) var(--ease-develop) both,
               wordmark-breathe 17s ease-in-out var(--dur-develop) infinite;
  }
  /* the crossover develops in as the name resolves out of the boot */
  @keyframes sweep { from { --sweep: -34%; } to { --sweep: 0%; } }
  /* the settled safelight, never quite still */
  @keyframes wordmark-breathe { 0%, 100% { --sweep: 0%; } 50% { --sweep: -10%; } }

  #skip-row { margin-top: var(--s5); }
  #post-boot { margin-top: var(--s6); }
  #post-boot .wordmark { margin-top: var(--s6); }
  .enter-row { margin-top: var(--s5); }
  .enter-row a { color: var(--developer); }

  h1, h2, h3 { font-family: var(--display); font-weight: 600; color: var(--bright); line-height: 1.1; text-wrap: balance; }
  h1 { font-size: var(--step-4); margin: var(--s6) 0 var(--s4); }
  h2 { font-size: var(--step-2); margin: var(--s6) 0 var(--s3); }
  h3 { font-size: var(--step-1); margin: var(--s5) 0 var(--s2); }

  /* ── the gate: a development step. underruled question, instrument input ── */
  .gate { margin: var(--s6) 0; max-width: var(--measure-tight); }
  .gate .q { font-family: var(--display); font-size: var(--step-2); color: var(--bright); margin: 0 0 var(--s3); }
  .gate-label { display: block; margin: 0 0 var(--s2); }
  .prompt { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
  .prompt .sigil { color: var(--developer); font-family: var(--mono); }
  .prompt input { flex: 1 1 16ch; min-width: 14ch; }
  .hint { min-height: 1.4em; margin: var(--s3) 0 0; color: var(--safelight-dim); text-transform: none; letter-spacing: 0.04em; }

  /* ── the faceless mark ── */
  .plate-bay {
    max-width: min(360px, 62%);
    margin: 0;
    border: var(--hair) solid var(--line);
    box-shadow: inset 0 0 0 4px var(--void), inset 0 0 0 5px var(--line);
    background: var(--surface);
  }
  .plate-bay img { display: block; width: 100%; }
  figure { margin: var(--s6) 0 0; }

  /* ── the enlarger hero: a wide sleeve for the 3D scene, above the name ── */
  .enlarger-bay {
    position: relative;
    margin: 0 0 var(--s5);
    aspect-ratio: 3 / 2;
    width: 100%;
    border: var(--hair) solid var(--line);
    box-shadow: inset 0 0 0 4px var(--void), inset 0 0 0 5px var(--line);
    background: var(--void);
    overflow: hidden;
  }
  /* Only the non-hidden child fills the sleeve. enlarger.js marks the other
     with [hidden] (the img in the live WebGL2 path; the canvas in every
     fallback: plain, reduced-motion, no-WebGL2). The :not([hidden]) scope
     keeps that attribute working -- a bare `display:block` here outranks the
     UA [hidden] rule and would leave an empty canvas stacked over the webp. */
  .enlarger-bay canvas:not([hidden]), .enlarger-bay img:not([hidden]) {
    display: block; width: 100%; height: 100%; object-fit: cover;
  }

  /* ── film edge code: a section divider in the machine's own numerals ── */
  .edgecode {
    font-family: var(--mono); font-size: var(--step--1);
    letter-spacing: 0.3em; color: var(--developer-dim);
    text-transform: uppercase; margin: var(--s6) 0;
    display: flex; align-items: center; gap: var(--s4);
  }
  .edgecode::before, .edgecode::after { content: ""; flex: 1; border-top: var(--hair) solid var(--line); }

  /* ── system footer ── */
  .sysfoot {
    position: relative;
    display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5);
    justify-content: space-between;
    max-width: var(--measure); margin: var(--s8) auto 0;
    padding-top: var(--s3); border-top: var(--hair) solid var(--line);
  }
  .sysfoot a { color: var(--muted); }
  .sysfoot a:hover { color: var(--bright); }
  /* the footer answers the header: a slower, dimmer trace the other way */
  .sysfoot::after {
    content: ""; position: absolute; left: 0; right: 0; top: -1px; height: 1px;
    pointer-events: none;
    background: linear-gradient(90deg, transparent, var(--developer-dim) 50%, transparent);
    background-size: 26% 100%; background-repeat: no-repeat;
    animation: densito-foot 13s var(--ease-settle) infinite;
  }
  @keyframes densito-foot {
    from { background-position: 126% 0; }
    to   { background-position: -26% 0; }
  }

  /* ── the two voices, made typographic ──────────────────────────────
     The operator is emulsion: his journal is set in a book face
     (Newsreader), warm, the human hand. The fleet is instrument: its
     records are mono, measured, developer-cold. This is the seam. ── */
  .operator {
    font-family: var(--serif);
    font-size: var(--step-1);
    line-height: 1.62;
    letter-spacing: 0;
    color: var(--fg);
    max-width: var(--measure);
  }
  .operator p { margin: 0 0 var(--s5); }
  .operator em { font-style: italic; color: var(--safelight); }

  .understudy {
    font-family: var(--mono);
    color: var(--fg);
    max-width: var(--measure);
  }
  .understudy p { margin: 0 0 var(--s4); }
  .understudy em { color: var(--developer); font-style: normal; }

  /* log metadata strip: a densitometer readout above each entry */
  .logmeta {
    display: block; margin: 0 0 var(--s5);
    padding-bottom: var(--s3); border-bottom: var(--hair) solid var(--line);
    color: var(--developer-dim); letter-spacing: 0.16em;
  }

  /* ── log plates: seeded headpiece, voice-mark, tailpiece (bin/gen-plates) ──
     Inline SVG with no colour literal: strokes are currentColor or a token.
     The DRAWN state is the base and the self-draw runs only under
     no-preference, so reduced-motion and plain mode land on a finished plate
     (a self-draw whose base is the undrawn state vanishes when motion is
     stripped). Voice picks the accent: operator amber, understudy cyan. */
  .genplate { margin: 0 0 var(--s6); border: var(--hair) solid var(--line);
           background: var(--surface); color: var(--fg); }
  .genplate svg { display: block; width: 100%; height: auto; }
  .genplate .s { fill: none; stroke: currentColor; stroke-width: 1.1;
              stroke-linecap: round; stroke-linejoin: round; }
  .genplate .a { stroke-width: 1.6; }
  .genplate .c { fill: none; stroke-width: 1.5; }
  .genplate[data-plate-voice="operator"] .a, .genplate[data-plate-voice="operator"] .c { stroke: var(--safelight); }
  .genplate[data-plate-voice="understudy"] .a, .genplate[data-plate-voice="understudy"] .c { stroke: var(--developer); }
  .genplate .h { fill: none; stroke: var(--line); stroke-width: 1; }
  .genplate .m { fill: none; stroke: var(--muted); stroke-width: 1;
              stroke-linecap: round; stroke-linejoin: round; }
  .genplate .d { fill: var(--muted); }
  .genplate .k { fill: none; stroke: var(--line-strong); stroke-width: 0.8; }
  .plate-mark { max-width: 15rem; margin: var(--s6) auto; border: 0;
                background: none; opacity: 0.85; }
  /* Wave I-2 plates: accent voices beyond the two log voices */
  .genplate[data-plate-voice="warm"] .c, .genplate[data-plate-voice="warm"] .g,
  .genplate[data-plate-voice="warm"] .p { stroke: var(--safelight); }
  .genplate[data-plate-voice="warm"] .p { fill: var(--safelight); }
  .genplate[data-plate-voice="system"] .c, .genplate[data-plate-voice="system"] .p { stroke: var(--developer); }
  .genplate[data-plate-voice="system"] .p { fill: var(--developer); }
  .genplate .b { fill: currentColor; }
  .genplate .f { fill: none; stroke: var(--line-strong); stroke-width: 1; }
  .genplate .f.o { stroke: var(--line); stroke-dasharray: 4 4; }
  .genplate .e { fill: none; stroke: var(--line-strong); stroke-width: 1; stroke-linecap: round; }
  .genplate .g { fill: none; stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; opacity: 0.9; }
  .genplate .x { fill: none; stroke: var(--line-strong); stroke-width: 1; }
  .genplate .p { stroke-width: 1; }
  .genplate .r { stroke-dasharray: 3 3; }
  /* .glow keeps its fill="url(#gradient)" attribute: a CSS fill here would replace the gradient with a flat disc */
  .plate-strip { border: 0; background: none; margin: var(--s7) 0 var(--s4); color: var(--developer-dim); }
  .plate-zone { margin: var(--s5) 0 0; }
  .plate-halation { border: 0; background: none; margin: 0 0 var(--s5); color: var(--safelight); }
  .plate-colophon { max-width: 6rem; margin: var(--s8) auto 0; border: 0; background: none; color: var(--muted); }
  .plate-curve { margin-bottom: var(--s7); }
  /* Wave I-3: the fleet dispatch graph */
  .genplate .n { fill: var(--surface); stroke: var(--fg); stroke-width: 1.4; }
  .genplate .n.w { stroke-dasharray: 3 3; fill: none; }
  .genplate .n.k { stroke: var(--line-strong); }
  .genplate .n.y { fill: none; stroke-width: 1; }
  .genplate[data-plate-voice="system"] .n.y { stroke: var(--safelight); }
  .genplate .q { stroke-dasharray: 2 5; opacity: 0.7; }
  .genplate .l { font: 600 13px var(--mono); letter-spacing: 0.12em; fill: var(--muted); text-anchor: middle; }
  /* the graph is a 1200-unit viewBox: at phone width its 13-unit labels render
     near 6px, so the labels and pulses scale up where the column is narrow */
  @media (max-width: 640px) {
    .genplate.plate-graph .l { font-size: 30px; letter-spacing: 0.06em; }
    .genplate.plate-graph .u { r: 6px; }
  }
  .plate-graph { margin: var(--s6) 0; }
  /* Wave I-4: the live tailpiece on /return. The still is the base; the canvas
     is un-hidden only on the live path (emulsion-tail.js), and both rules are
     scoped :not([hidden]) because an author display rule outranks the UA
     [hidden] rule by cascade origin. */
  .plate-rd { aspect-ratio: 32 / 9; overflow: hidden; margin: var(--s7) 0 var(--s6); }
  .plate-rd canvas:not([hidden]), .plate-rd img:not([hidden]) {
    display: block; width: 100%; height: 100%; object-fit: cover; image-rendering: pixelated;
  }
  /* Wave I-4: three misregistered ghost exposures over the quorum plate; they
     converge and fade once an ending is cast (quorum-ghosts.js sets the state). */
  .plate-bay[data-ghosts] { position: relative; }
  .plate-bay .ghost { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0.26;
    pointer-events: none; transition: transform 1.8s ease-in-out, opacity 1.8s ease-in-out;
    transform: translate(var(--gx), var(--gy)); }
  .plate-bay .g1 { --gx: -3.2%; --gy: 1.6%; }
  .plate-bay .g2 { --gx: 2.6%; --gy: -2.2%; }
  .plate-bay .g3 { --gx: 1.1%; --gy: 3.1%; }
  .plate-bay[data-ghosts="converged"] .ghost { transform: none; opacity: 0; }
  /* hatched nameplates on the fleet cards */
  .fleet .artifact-code { display: inline-block; padding: 0.25rem 0.6rem; border: var(--hair) solid var(--line);
    background: repeating-linear-gradient(135deg, transparent 0 5px, var(--surface-2) 5px 6px); }
  @media (prefers-reduced-motion: no-preference) {
    .plate-halation .glow { animation: halation-breathe 11s ease-in-out infinite; }
    @keyframes halation-breathe { 50% { opacity: 0.55; } }
  }
  .plate-tail { margin-top: var(--s7); }
  /* pinhole vignette: every plate on /plate is shot through the same aperture */
  .next-plate-bay { position: relative; }
  .next-plate-bay::after { content: ""; position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(ellipse at center, transparent 58%, var(--void) 100%); opacity: 0.85; }
  @media (prefers-reduced-motion: no-preference) {
    /* the load-time self-draw: the fallback where scroll timelines are absent */
    .plate-head .s, .plate-tail .t, .plate-curve .t, .plate-graph .t {
      stroke-dasharray: 1600; animation: plate-draw 2.4s ease-out both;
      animation-delay: calc(var(--i, 0) * 70ms);
    }
    @keyframes plate-draw { from { stroke-dashoffset: 1600; } }

    /* ── Wave I-5: the plates develop as the reader arrives ──
       Scroll-driven where the engine has view() timelines (Chrome, Safari 26):
       every plate draws itself as it enters the viewport instead of at load,
       the test strips fill band by band, the zone wedge slides with the
       scroll, and the accent ticks settle like a densitometer needle. All of
       it sits inside the no-preference query on purpose: a scroll timeline
       ignores animation-duration, so the global reduced-motion rule alone
       would not stop it. Where view() is absent the drawn state is the base
       and nothing here applies, which reads as intentional stillness.
       animation-timeline is declared after the shorthand because the
       shorthand resets it. */
    @supports (animation-timeline: view()) {
      /* the FIGURE is the timeline subject: a path is its own view() subject and
         a stroke near the top of a plate would finish within a few pixels */
      .genplate { view-timeline-name: --plate; view-timeline-axis: block; }
      .plate-head .s, .plate-tail .t, .plate-curve .t, .plate-graph .t {
        animation: plate-draw linear both; animation-timeline: --plate;
        animation-range: entry 8% entry 92%;
      }
      .plate-mark .m, .plate-mark .k {
        stroke-dasharray: 140; animation: plate-draw-short linear both;
        animation-timeline: --plate; animation-range: entry calc(20% + var(--i, 0) * 6%) entry 100%;
      }
      @keyframes plate-draw-short { from { stroke-dashoffset: 140; } }
      .plate-contact .g {
        stroke-dasharray: 420; animation: pencil linear both;
        animation-timeline: --plate; animation-range: entry calc(30% + var(--i, 0) * 6%) cover 45%;
      }
      @keyframes pencil {
        from { stroke-dashoffset: 420; stroke-width: 1.2; }
        55%  { stroke-width: 3.2; }
        to   { stroke-dashoffset: 0; stroke-width: 2.4; }
      }
      .plate-strip .b {
        animation: strip-fill linear both; animation-timeline: --plate;
        animation-range: entry calc(var(--i, 0) * 5%) entry 100%;
      }
      @keyframes strip-fill { from { fill-opacity: 0; } }
      .genplate.plate-zone .w {
        animation: wedge linear both; animation-timeline: --plate;
        animation-range: entry 0% exit 100%; offset-rotate: 0deg; opacity: 1;
      }
      @keyframes wedge { from { offset-distance: 0%; } to { offset-distance: 100%; } }
      .plate-tail .c, .plate-curve .c {
        transform-box: fill-box; transform-origin: 50% 100%;
        animation: needle linear both; animation-timeline: --plate;
        animation-range: entry 40% entry 100%;
      }
      @keyframes needle { from { transform: scaleY(0); } 70% { transform: scaleY(1.12); } to { transform: none; } }
    }

    /* the two loops: sprocket judder on every film-edge rail (a 1px clunk,
       stepped, every 4.7 s: no small-integer ratio with the site's other
       loops) and one dispatch pulse riding the fleet graph's edges in turn */
    .genplate .h { animation: sprocket 4.7s steps(2, jump-end) infinite; }
    @keyframes sprocket { 0%, 86% { transform: translateY(0); } 87%, 100% { transform: translateY(1px); } }
    .genplate .u { animation: fleet-pulse 23.4s linear infinite; animation-delay: calc(var(--i, 0) * -2.6s); }
    @keyframes fleet-pulse {
      0%   { offset-distance: 0%;   opacity: 0; }
      1%   { opacity: 1; }
      10%  { offset-distance: 100%; opacity: 1; }
      11%  { offset-distance: 100%; opacity: 0; }
      100% { offset-distance: 100%; opacity: 0; }
    }
  }
  /* base state of the two travelling marks is HIDDEN: they exist only while
     animated, so reduced motion and engines without view() never show nine
     pulses parked on their nodes or a wedge parked at zone 0 */
  .genplate .rail { fill: none; stroke: none; }
  .genplate .w { fill: var(--developer); opacity: 0; }
  .genplate .u { fill: var(--developer); opacity: 0; offset-rotate: 0deg; }

  .subtitle { font-family: var(--mono); font-size: var(--step--1); letter-spacing: 0.12em;
              text-transform: uppercase; color: var(--developer); margin: 0 0 var(--s6); }
  .wide { max-width: 82ch; }

  /* ── the recovered-fragments index: a strip of plate sleeves ── */
  .index { list-style: none; margin: var(--s5) 0; padding: 0; }
  .index li { margin: 0; }
  .index li + li { margin-top: calc(-1 * var(--hair)); }
  .index li .row {
    display: grid; grid-template-columns: 5.5rem 1fr auto;
    gap: var(--s3) var(--s5); align-items: baseline;
    padding: var(--s3) var(--s4); border: var(--hair) solid var(--line);
    color: var(--muted);
  }
  .index li .row .title { color: var(--muted); font-style: italic; }
  .index a {
    display: grid;
    grid-template-columns: 5.5rem 1fr auto;
    gap: var(--s3) var(--s5);
    align-items: baseline;
    padding: var(--s3) var(--s4);
    border: var(--hair) solid var(--line);
    text-decoration: none;
    transition: border-color var(--dur-tick) var(--ease-settle),
                background var(--dur-tick) var(--ease-settle);
  }
  .index a:hover { border-color: var(--line-strong); background: var(--surface); }
  .index .id { font-family: var(--mono); font-size: var(--step--1); letter-spacing: 0.14em; color: var(--developer); }
  .index .title { font-family: var(--display); font-size: var(--step-1); color: var(--bright); }
  .index .state { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); text-align: right; }
  @media (max-width: 34rem) {
    .index a { grid-template-columns: 1fr auto; }
    .index .title { grid-column: 1 / -1; }
  }

  /* ── the fragment meter: exposure ticks on the instrument ── */
  .progress { list-style: none; display: flex; gap: var(--s2); padding: 0; margin: var(--s4) 0; }
  .progress li {
    width: 0.9rem; height: 0.9rem;
    border: var(--hair) solid var(--line-strong);
  }
  .progress li[data-lit], .progress li.lit { background: var(--developer); border-color: var(--developer); }
  .progress-label { color: var(--muted); }

  /* ── the revision diff: LEDGER's two columns. added rows develop in ── */
  .diff { overflow-x: auto; border: var(--hair) solid var(--line); background: var(--surface); margin: var(--s5) 0; }
  .diff table { border-collapse: collapse; width: 100%; font-family: var(--mono); font-size: var(--step--1); }
  .diff td { padding: 0.2rem var(--s4); white-space: pre; vertical-align: top; color: var(--muted); }
  .diff td.g { width: 6.5rem; color: var(--developer-dim); user-select: none; }
  .diff tr.add td { color: var(--developer-bright); background: color-mix(in oklab, var(--developer) 8%, transparent); }
  .diff tr.add td.g { color: var(--developer); }
  .diff-instruction { color: var(--developer-dim); margin: 0 0 var(--s2); }

  /* archive list-item state spans (progress/locked rows) */
  .row { display: flex; justify-content: space-between; gap: var(--s4); }
  .locked { color: var(--muted); }
  .state { color: var(--muted); }

  /* ══ Acts III–V interactive components ═══════════════════════════════
     Bespoke v1 panels are mapped onto the primitives + tokens rather than
     re-bespoked. Every panel is a plate sleeve; every control is an
     instrument button; every readout is mono. ── */

  /* all the *-panel / card / record surfaces → the plate sleeve */
  .drone-panel, .sonification-panel, .plate-hud, .plate-approval,
  .flinch-lab, .media-object, .artifact, .artifact-card, .record,
  .november-room, .evidence-intro, .blind-test, .next-plate-bay {
    position: relative;
    background: var(--surface);
    background-image: var(--sleeve-paint, none);
    border: var(--hair) solid var(--line);
    padding: var(--s5);
    margin: var(--s5) 0;
  }

  /* the plate sleeve lifts toward the safelight when you reach for it.
     fine-pointer only (never a touch tap-target), reduced-motion frozen by
     the global rule; .record is omitted so a nested fleet record never
     double-lifts inside its own .artifact. */
  @media (hover: hover) and (pointer: fine) {
    .drone-panel, .sonification-panel, .plate-hud, .plate-approval,
    .flinch-lab, .media-object, .artifact, .artifact-card,
    .november-room, .evidence-intro, .blind-test, .next-plate-bay {
      transition: transform var(--dur-tick) var(--ease-settle),
                  border-color var(--dur-tick) var(--ease-settle),
                  box-shadow var(--dur-tick) var(--ease-settle);
    }
    .drone-panel:hover, .sonification-panel:hover, .plate-hud:hover,
    .plate-approval:hover, .flinch-lab:hover, .media-object:hover,
    .artifact:hover, .artifact-card:hover, .november-room:hover,
    .evidence-intro:hover, .blind-test:hover, .next-plate-bay:hover {
      transform: translateY(-2px);
      border-color: var(--safelight-dim);
      box-shadow: 0 6px 22px -12px var(--safelight-veil);
    }
  }

  /* all the *-btn controls → the instrument button */
  .drone-btn, .sonification-btn, .speed-btn, .slope-step, .projection-btn,
  .hud-toggle-btn, .approve-plate, .plate-call, .cast, .blind-actions button,
  .download-list a, .download-actions button {
    font-family: var(--mono); font-size: var(--step--1);
    letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--fg); background: transparent;
    border: var(--hair) solid var(--line-strong);
    padding: var(--s2) var(--s4); cursor: pointer;
    text-decoration: none; display: inline-block;
    transition: color var(--dur-tick) var(--ease-settle),
                border-color var(--dur-tick) var(--ease-settle),
                background var(--dur-tick) var(--ease-settle);
  }
  .drone-btn:hover, .sonification-btn:hover, .speed-btn:hover, .slope-step:hover,
  .projection-btn:hover, .hud-toggle-btn:hover, .approve-plate:hover,
  .plate-call:hover, .cast:hover, .blind-actions button:hover,
  .download-list a:hover, .download-actions button:hover {
    color: var(--bright); border-color: var(--bright); background: var(--surface);
  }

  /* headers/labels/readouts inside panels → chrome mono */
  .drone-header, .sonification-controls, .artifact-code, .provenance-label,
  .provenance-specs, .provenance-meta, .drone-vol-display, .hud-control-bay,
  .plate-totals, .flinch-controls, .blind-status, .atlas-h {
    font-family: var(--mono); font-size: var(--step--1);
    letter-spacing: 0.1em; color: var(--muted);
  }
  .drone-description, .drone-transcript, .flinch-comparison { color: var(--fg); }

  /* the atmosphere synthesizer is optional ambiance: collapsed to a single
     summary bar by default so it never crowds the decision above it. */
  summary.drone-header { cursor: pointer; list-style-position: inside; }
  summary.drone-header .drone-indicator { margin-left: var(--s3); }
  details.drone-panel:not([open]) { padding-block: var(--s3); }
  details.drone-panel[open] > summary.drone-header { margin-bottom: var(--s4); }

  /* live captions / status regions → developer voice, the machine reporting */
  .drone-captions, .sonification-caption, .sonification-status,
  .drone-indicator, .provenance-origin, .slope-step-freq {
    font-family: var(--mono); color: var(--developer); font-size: var(--step--1);
  }

  /* range inputs (volume, speed) → a developer-tinted instrument slider */
  input[type="range"] { accent-color: var(--developer); }

  /* ── canvas bays: contact sheet, plate generator, the hero plate ── */
  .plate-bay, .next-plate-bay, .plate-hud canvas, #plate-canvas, .ledger-bay {
    border: var(--hair) solid var(--line);
    box-shadow: inset 0 0 0 4px var(--void), inset 0 0 0 5px var(--line);
    background: var(--void);
  }
  /* :not([hidden]) so a canvas a script marks hidden for its fallback path
     actually collapses. An author `display` here beats the UA [hidden] rule
     by cascade origin, not specificity, so [hidden] alone cannot win it back. */
  canvas:not([hidden]) { max-width: 100%; display: block; }
  .next-plate-static, .plate-approval img { max-width: 100%; }
  .approval-stamp {
    font-family: var(--mono); letter-spacing: 0.2em; text-transform: uppercase;
    color: var(--developer); border: var(--hair) solid var(--developer-dim);
    padding: var(--s1) var(--s3); display: inline-block;
  }

  /* ── the gallery text-render plates ── */
  .plates { display: grid; gap: var(--s5); grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); margin: var(--s5) 0; }
  .text-render {
    border: var(--hair) solid var(--line);
    box-shadow: inset 0 0 0 3px var(--void), inset 0 0 0 4px var(--line);
    background: var(--surface); padding: var(--s4);
  }
  .pt { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); }

  /* ── the underexposed paragraph: Gate 4. hidden by contrast, not removal.
     Screen readers still get it; the high-contrast media query repaints it. ── */
  .underexposed { color: #0a0a0a; }
  @media (prefers-contrast: more) { .underexposed { color: var(--muted); } }

  /* ── data tables (evidence, forensic) ── */
  .data-region { overflow-x: auto; border: var(--hair) solid var(--line); margin: var(--s5) 0; }
  .data-table { border-collapse: collapse; width: 100%; font-family: var(--mono); font-size: var(--step--1); }
  .data-table caption { text-align: left; color: var(--muted); padding: var(--s3) var(--s4); }
  .data-table th, .data-table td { border: var(--hair) solid var(--line); padding: var(--s2) var(--s4); text-align: left; vertical-align: top; color: var(--fg); }
  .data-table th { color: var(--developer); font-weight: 600; }
  .data-table .correct, .correct { color: var(--developer); }
  .data-table .active, tr.active td { background: color-mix(in oklab, var(--developer) 8%, transparent); }

  /* ── evidence provenance badges ── */
  .provenance-badge { border: var(--hair) solid var(--line); padding: var(--s3) var(--s4); margin: var(--s3) 0; }
  .provenance-hash code { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); word-break: break-all; }
  .evidence-nav { list-style: none; display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); padding: 0; margin: var(--s4) 0; }
  .evidence-nav a { color: var(--muted); font-family: var(--mono); font-size: var(--step--1); }
  .section-rule { border: 0; border-top: var(--hair) solid var(--line); margin: var(--s6) 0; }
  .tree { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); white-space: pre; overflow-x: auto; }

  /* ── the flinch lab cue list ── */
  .flinch-cue-list { list-style: none; padding: 0; margin: var(--s4) 0; }
  .flinch-cue-item { font-family: var(--mono); font-size: var(--step--1); color: var(--muted); padding: var(--s1) 0; border-bottom: var(--hair) solid var(--line); }
  .flinch-captions { font-family: var(--mono); color: var(--developer); }
  .flinch-field { color: var(--fg); }

  /* ── projection bar ── */
  .projection-bar { display: flex; gap: var(--s3); margin: var(--s5) 0; padding-bottom: var(--s3); border-bottom: var(--hair) solid var(--line); }

  /* ── the quorum casting choices ── */
  .choices { display: grid; gap: var(--s5); margin: var(--s6) 0; }
  @media (min-width: 46rem) { .choices { grid-template-columns: 1fr 1fr; } }
  .choice {
    display: block; text-align: left; width: 100%;
    background: var(--surface); border: var(--hair) solid var(--line-strong);
    padding: var(--s5); cursor: pointer; color: var(--fg);
    font-family: var(--mono); font-size: var(--step-0); line-height: 1.6;
    transition: border-color var(--dur-settle) var(--ease-settle),
                background var(--dur-settle) var(--ease-settle);
  }
  .choice:hover { border-color: var(--bright); background: var(--surface-2); }
  .ct { font-family: var(--display); font-size: var(--step-2); color: var(--bright); display: block; margin-bottom: var(--s3); }
  .cd { display: block; color: var(--muted); }
  .cl { font-family: var(--mono); font-size: var(--step--1); letter-spacing: 0.14em; text-transform: uppercase; color: var(--developer); display: block; margin-top: var(--s3); }
  .cact { display: block; margin-top: var(--s4); }
  .evidence-recap { color: var(--muted); font-family: var(--mono); font-size: var(--step--1); }
  .op-choice .ct, .op-choice .cl { color: var(--safelight); }
  .machine-choice .ct, .machine-choice .cl { color: var(--developer); }

  /* ── november wall labels ── */
  .wall-labels { list-style: none; padding: 0; margin: var(--s5) 0; display: grid; gap: var(--s4); }
  .wall-labels li { border-left: var(--hair) solid var(--line-strong); padding-left: var(--s4); color: var(--fg); }
  .operator-block { color: var(--safelight); font-family: var(--serif); font-size: var(--step-1); }

  /* ── the return: the real payload, set as a plate to be read once ── */
  .big-quote {
    font-family: var(--display); font-style: italic; font-weight: 400;
    font-size: var(--step-3); line-height: 1.2; color: var(--safelight);
    max-width: 24ch; margin: var(--s7) 0; text-wrap: balance;
  }
  .decision-receipt {
    font-family: var(--mono); font-size: var(--step--1); color: var(--muted);
    border: var(--hair) solid var(--line); padding: var(--s4); margin: var(--s6) 0;
  }
.continue-field { margin: var(--s6) 0; border: var(--hair) solid var(--line); box-shadow: inset 0 0 0 4px var(--void), inset 0 0 0 5px var(--line); background: var(--void); }
  .continue-field canvas { display: block; width: 100%; height: clamp(220px, 34vh, 380px); }
    .plate-1205 { border: var(--hair) solid var(--line); box-shadow: inset 0 0 0 4px var(--void), inset 0 0 0 5px var(--line); background: var(--void); margin: var(--s6) 0; }
  .plate-1205 img, .plate-1205 canvas { display: block; width: 100%; }

  /* ══ RESTORE self-drawing medallion (return.html) ══════════════════════
     A geometric identity plate -- a compass-and-straightedge rosette inside
     a Greek-key ring -- inline SVG that DRAWS ITSELF IN via stroke-dashoffset
     as the figure scrolls into view, so the plate reads "render complete"
     the instant the operator returns. It sits behind the completed .webp,
     which carries the actual meaning; the SVG is decorative (aria-hidden).

     Reduced-motion safety: the global @layer modes rule freezes every
     animation to 0.001ms, which would strand a full->0 self-draw at its
     FIRST frame (invisible). So the RESTING state below is the COMPLETED
     figure (stroke-dashoffset: 0, fully inked). The draw-in only exists
     inside @media (prefers-reduced-motion: no-preference) + @supports, so
     under reduced motion or an unsupporting browser the medallion is simply
     already drawn. Nothing to freeze, nothing to strand. */
  .plate-develop { position: relative; }
  .plate-stack { position: relative; display: block; width: min(320px, 66%); margin: 0 auto; }
  .plate-stack img { position: relative; z-index: 1; }
  .plate-glyph {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: 132%; aspect-ratio: 1 / 1; height: auto;
    z-index: 0; pointer-events: none; overflow: visible;
  }
  .plate-glyph * { fill: none; }
  /* resting = completed. scaffold has already settled toward --line; the ink
     rosette + Greek-key ring stand in monochrome. */
  .plate-glyph .scaf { stroke: var(--line); stroke-width: 0.9; opacity: 0.5; }
  .plate-glyph .ink { stroke: var(--line-strong); stroke-width: 1.2; }
  .plate-glyph .ink .seed { stroke: var(--fg); stroke-width: 1; opacity: 0.85; }
  .plate-glyph .d {
    stroke-linecap: round; stroke-linejoin: round;
    stroke-dasharray: 100; stroke-dashoffset: 0;
  }

  /* the ink: developer-cyan scaffold constructs first, then the monochrome
     figure inks over it, then the scaffold settles from cyan toward --line. */
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {
      .plate-glyph .d {
        stroke-dashoffset: 100;                 /* start undrawn (fill: both holds it) */
        animation: plate-ink linear both;
        animation-timeline: view();
        animation-range: entry 6% cover 42%;
      }
      /* scaffold draws first and in cyan ... */
      .plate-glyph .scaf { stroke: var(--developer); opacity: 0.75; }
      .plate-glyph .scaf .d { animation-range: entry 2% cover 22%; }
      /* ... the inked figure draws over it a beat later ... */
      .plate-glyph .ink .d { animation-range: entry 16% cover 46%; }
      /* ... then the whole scaffold settles from cyan toward --line, receding
         behind the finished monochrome mark. */
      .plate-glyph .scaf {
        animation: plate-settle linear both;
        animation-timeline: view();
        animation-range: cover 34% cover 66%;
      }
    }
  }
  @keyframes plate-ink { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
  @keyframes plate-settle {
    from { stroke: var(--developer); opacity: 0.75; }
    to   { stroke: var(--line); opacity: 0.5; }
  }

  /* accent roles: developer = the system speaks, only. */
  .machine, [data-voice="machine"] { color: var(--developer); }
  [data-voice="operator"] { color: var(--safelight); }
}

@layer pages {
  /* ══ the five motion moments ═══════════════════════════════════════════
     Cross-document view transitions turn 23 files into one continuous
     artifact: each navigation is a plate advancing, a soft settle and an
     exposure crossfade. Unsupported browsers navigate normally. ── */

  /* the epoch clock, finally ticking (live-chrome.js repaints the values) */
  [data-uptime], [data-session], [data-plates] { font-variant-numeric: tabular-nums; }

  /* the revisit notice develops in rather than hard-cutting */
  .revisit-develop { animation: develop var(--dur-develop) var(--ease-develop) both; }
  @keyframes develop {
    from { opacity: 0; filter: contrast(0.4) brightness(0.6); }
    to   { opacity: 1; filter: none; }
  }

  /* a gate's accepted answer, and the section it unseals, develop in */
  [data-requires]:not([hidden]) { animation: develop var(--dur-develop) var(--ease-develop) both; }
}

@view-transition { navigation: auto; }
::view-transition-old(root) { animation: vt-out 300ms var(--ease-settle) both; }
::view-transition-new(root) { animation: vt-in var(--dur-settle) var(--ease-develop) both; }
@keyframes vt-out { to { opacity: 0; transform: translateY(-6px); } }
@keyframes vt-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

@layer modes {
  @media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) { animation: none !important; }
    *, *::before, *::after {
      animation-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.001ms !important;
    }
    #terminal .cursor { animation: none; }
  }

  /* plain mode: strip the darkroom, keep the words. token overrides only. */
  :root:has(body.plain), body.plain {
    --safelight-veil: transparent;
    --developer-veil: transparent;
  }
  body.plain::before, body.plain::after { display: none; }
  body.plain #terminal::after { display: none; }
  /* the instrument traces are motion chrome, not content: strip them in plain */
  body.plain .statusbar::after, body.plain .sysfoot::after { display: none; }
  /* plain keeps the resolved wordmark, but not its perpetual breathing */
  body.plain .wordmark .w2 { animation: none; --sweep: 0%; }
  /* plain strips the v4 instrument frame + film damage (all motion chrome) */
  :root:has(body.plain)::before, :root:has(body.plain)::after,
  body.plain main::before, body.plain .sysfoot::before { display: none; }
  body.plain h1, body.plain h2 { animation: none; }
  /* plain strips the Houdini emulsion grain from plate sleeves */
  body.plain { --sleeve-paint: none; }
  /* plain keeps the medallion as a finished static plate, never mid-draw:
     kill the self-draw and settle animations and land on the completed figure
     (the .webp already carries the meaning, so this is pure structure). */
  body.plain .plate-glyph .d { animation: none; stroke-dashoffset: 0; }
  body.plain .plate-glyph .scaf { animation: none; stroke: var(--line); opacity: 0.5; }
  /* plain keeps the log plates as finished stills, never mid-draw */
  body.plain .genplate .s, body.plain .genplate .t, body.plain .genplate .m, body.plain .genplate .k,
  body.plain .genplate .g { animation: none; stroke-dasharray: none; }
  body.plain .genplate .h, body.plain .genplate .b, body.plain .genplate .c { animation: none; }
  body.plain .genplate .u, body.plain .genplate .w { display: none; }
  body.plain .plate-bay .ghost { display: none; }

  @media print {
    :root {
      --void: #ffffff; --surface: #ffffff; --surface-2: #ffffff;
      --fg: #000000; --bright: #000000; --muted: #333333;
      --line: #000000; --line-strong: #000000;
      --safelight: #000000; --developer: #000000; --focus: #000000;
    }
    html::before, html::after, body::before, body::after, #terminal::after,
    .statusbar::after, .sysfoot::before,
    .sysfoot::after { display: none; }
    .wordmark .w2 { -webkit-text-fill-color: #000; color: #000; }
    a { color: #000; }
    /* every printed page circulates as a detachable object and must say
       what it is on its face. */
    main::after {
      content: "THE LONG EXPOSURE IS A WORK OF FICTION. NOBODY IS MISSING. whereishairglasses.com";
      display: block; margin-top: 2rem; padding-top: 0.5rem;
      border-top: 1px solid #000; font-family: var(--mono); font-size: 9pt;
    }
  }
}
