/* THE SITE STYLESHEET.
 *
 * Every colour, size, weight, tracking and spacing value here is a token
 * reference. There are no literals: `npm run lint:tokens` fails on a hex,
 * rgb(), hsl(), named colour, gradient stop, raw px font-size, raw px margin,
 * inline style or SVG fill found outside tokens/ (1D-13).
 *
 * The two load-bearing risks (G11) are implemented here and nowhere else:
 *
 *   RISK 1 — the hero is a live 3D canvas carrying no marketing overlay. The
 *   first read is the machine and its provenance line, not a value
 *   proposition. Everything under `.hero` exists to make that survivable when
 *   WebGL, JavaScript or motion is unavailable.
 *
 *   RISK 2 — provenance is set at the same typographic tier as the title.
 *   `.provenance .authority` deliberately shares the h3 size and sits directly
 *   under the machine name rather than in a footnote. Legal and provenance
 *   text is demoted on every comparable site; on this one it is the product.
 */

/* ── self-hosted variable faces (G1). No CDN, ever (non-negotiable 4). ──── */
@font-face {
  font-family: 'Archivo Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('/assets/fonts/archivo-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Newsreader Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 200 800;
  src: url('/assets/fonts/newsreader-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Martian Mono Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 800;
  src: url('/assets/fonts/martian-mono-latin-wght-normal.woff2') format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

/* ── reset ──────────────────────────────────────────────────────────────── */
*,
*::before,
*::after {
  box-sizing: border-box;
}
html {
  -webkit-text-size-adjust: 100%;
}
body {
  margin: 0;
}
img,
canvas,
svg {
  max-width: 100%;
  display: block;
}
a {
  color: inherit;
}
button {
  font: inherit;
  color: inherit;
}

/* ── ground and texture (G3, G4) ────────────────────────────────────────── *
 * The texture system is a drafting sheet: a fine rule at --g-grid-fine and a
 * coarse rule at --g-grid-coarse, in the hairline colour, over the cool
 * ground — the ruled paper the source drawings were made on. It is built from
 * gradients whose every stop is a token, so it survives the token lint.       */
body {
  background-color: var(--g-ink-900);
  background-image:
    repeating-linear-gradient(
      to right,
      var(--g-line-strong) 0,
      var(--g-line-strong) var(--g-hairline),
      transparent var(--g-hairline),
      transparent var(--g-grid-coarse)
    ),
    repeating-linear-gradient(
      to bottom,
      var(--g-line-strong) 0,
      var(--g-line-strong) var(--g-hairline),
      transparent var(--g-hairline),
      transparent var(--g-grid-coarse)
    );
  background-attachment: fixed;
  color: var(--g-text);
  font-family: var(--g-face-body);
  font-size: var(--g-size-body);
  line-height: var(--g-leading-body);
  font-weight: var(--g-weight-body);
}

/* The grain: a second, finer rule at a shallow angle, kept faint. Together
   with the grid this is the one texture system, derived from the subject. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  opacity: var(--g-grain-opacity);
  background-image: repeating-linear-gradient(
    -18deg,
    var(--g-text-faint) 0,
    var(--g-text-faint) var(--g-hairline),
    transparent var(--g-hairline),
    transparent var(--g-grid-fine)
  );
}

/* ── layout: the rail (G10) ─────────────────────────────────────────────── *
 * The nav is not a top bar. It is a fixed left rail carrying the collection
 * ordinals in mono, rotated to read up the edge of the page — the way a set of
 * drawings is indexed down the margin of a sheet. That is the non-default
 * treatment G10 asks a plan to name, and it is present in the rendered DOM.   */
.rail {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: var(--g-rail);
  border-right: var(--g-hairline) solid var(--g-line);
  background-color: var(--g-ink-800);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: var(--g-space-4) 0;
  z-index: 3;
}
.rail a.mark {
  font-family: var(--g-face-display);
  font-weight: var(--g-weight-display);
  font-size: var(--g-size-h3);
  letter-spacing: var(--g-track-display);
  text-decoration: none;
  color: var(--g-text);
}
.rail nav {
  flex: 1;
  display: flex;
  align-items: center;
}
/* The rotation belongs on the LINK, not on the list.
   `writing-mode: vertical-rl` on the <ol> turns its block axis horizontal, so
   the four items laid out as side-by-side COLUMNS inside a 4.5rem rail — the
   first one ("01 Space") was pushed clean off the left edge of the viewport
   and the rest overlapped. Setting the vertical writing mode on each anchor
   keeps the list a normal vertical stack and rotates only the text. */
.rail nav ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--g-space-5);
}
.rail nav a {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
}
.rail nav a {
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  font-weight: var(--g-weight-label);
  letter-spacing: var(--g-track-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--g-text-dim);
  transition: color var(--g-dur-micro) var(--g-ease-out);
  padding: var(--g-space-2);
}
.rail nav a:hover,
.rail nav a[aria-current='page'] {
  color: var(--g-accent-bright);
}
.rail .count {
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  color: var(--g-text-faint);
  writing-mode: vertical-rl;
  letter-spacing: var(--g-track-label);
}

main {
  margin-left: var(--g-rail);
  position: relative;
  z-index: 1;
}
.wrap {
  max-width: var(--g-measure-wide);
  margin: 0 auto;
  padding: 0 var(--g-space-6);
}

/* ── type (G2) ──────────────────────────────────────────────────────────── */
h1,
h2,
h3 {
  font-family: var(--g-face-display);
  font-weight: var(--g-weight-display);
  letter-spacing: var(--g-track-display);
  line-height: var(--g-leading-tight);
  margin: 0;
}
h1 {
  font-size: var(--g-size-display-sm);
  line-height: var(--g-leading-display);
}
h2 {
  font-size: var(--g-size-h2);
}
h3 {
  font-size: var(--g-size-h3);
  font-weight: var(--g-weight-strong);
}
p {
  margin: 0 0 var(--g-space-4);
  max-width: var(--g-measure);
}
.label {
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  font-weight: var(--g-weight-label);
  letter-spacing: var(--g-track-label);
  text-transform: uppercase;
  color: var(--g-text-dim);
  margin: 0 0 var(--g-space-3);
}
.dim {
  color: var(--g-text-dim);
}
.small {
  font-size: var(--g-size-small);
}

/* ── the hero: object-scene (G5). RISK 1 lives here. ────────────────────── */
/* The stage takes what is LEFT after the plate, never the other way round.
   With `min-height: 52vh` on the stage and `88vh` on the hero, the two minima
   summed past the viewport and pushed the h1 below the fold, where it was cut
   in half — the headline of a hero that is deliberately headline-last. The
   canvas row is now `minmax(0, 1fr)`, so the plate is always fully visible and
   the object gets the remainder. */
.hero {
  min-height: 100svh;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  border-bottom: var(--g-hairline) solid var(--g-line);
  position: relative;
}
.hero-stage {
  position: relative;
  min-height: 0;
  background-color: var(--g-ink-800);
  overflow: hidden;
}
/* THE STAGE IS A REGION ITS CHILDREN FILL — none of them sizes it.
 *
 * Two separate bugs came from letting stage children sit in normal flow:
 *   - a canvas with `height: 100%` inside a `minmax(0, 1fr)` row resolves its
 *     percentage against a row whose height depends on the canvas, and the
 *     browser breaks the circularity with the canvas's intrinsic attribute
 *     size, inflating the row;
 *   - the static fallback carries a 1200×630 still, so before script hid it the
 *     stage was as tall as the image and the headline plate was pushed off the
 *     bottom of the viewport — including permanently, with JavaScript disabled.
 *
 * Taking every child out of flow makes the hero exactly one viewport tall in
 * all three states — loading, fallback and live canvas. */
.hero-stage > * {
  position: absolute;
  inset: 0;
}
.hero-stage canvas {
  width: 100%;
  height: 100%;
  display: block;
}
.hero-stage #viewer-fallback {
  overflow: auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.hero-stage #viewer-fallback img {
  max-height: 60%;
  width: auto;
  object-fit: contain;
  margin: 0 auto;
}
.hero-plate {
  padding: var(--g-space-6) 0 var(--g-space-7);
}
.hero h1 {
  max-width: var(--g-measure-wide);
}
.hero .standfirst {
  margin-top: var(--g-space-5);
  color: var(--g-text-dim);
}

/* ── the loading state, as a designed object (G10) ──────────────────────── *
 * It is the first thing every visitor sees, so it is not a spinner: it is a
 * mono readout of what is actually being fetched, which is also the honest
 * thing to show for a catalogue whose whole argument is disclosure.           */
.loading {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  padding: var(--g-space-6);
  gap: var(--g-space-2);
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  letter-spacing: var(--g-track-label);
  color: var(--g-text-dim);
  background-color: var(--g-ink-800);
}
.loading[hidden] {
  display: none;
}
.loading .bar {
  width: 100%;
  max-width: var(--g-measure);
  height: var(--g-hairline);
  background-color: var(--g-line);
  position: relative;
}
.loading .bar > i {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0;
  background-color: var(--g-accent);
  transition: width var(--g-dur-micro) var(--g-ease-out);
}

/* ── CTA as a designed object (G10): corner ticks, no pill ──────────────── */
.cta {
  display: inline-block;
  position: relative;
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  font-weight: var(--g-weight-label);
  letter-spacing: var(--g-track-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--g-accent-bright);
  background: transparent;
  border: var(--g-hairline) solid var(--g-accent-dim);
  border-radius: var(--g-radius);
  padding: var(--g-space-3) var(--g-space-5);
  cursor: pointer;
  transition:
    background-color var(--g-dur-micro) var(--g-ease-out),
    border-color var(--g-dur-micro) var(--g-ease-out);
}
.cta::before,
.cta::after {
  content: '';
  position: absolute;
  width: var(--g-tick);
  height: var(--g-tick);
  border-color: var(--g-accent);
  border-style: solid;
}
.cta::before {
  top: calc(-1 * var(--g-hairline));
  left: calc(-1 * var(--g-hairline));
  border-width: var(--g-hairline) 0 0 var(--g-hairline);
}
.cta::after {
  bottom: calc(-1 * var(--g-hairline));
  right: calc(-1 * var(--g-hairline));
  border-width: 0 var(--g-hairline) var(--g-hairline) 0;
}
.cta:hover {
  background-color: var(--g-accent-wash);
  border-color: var(--g-accent);
}
:where(a, button, input, [tabindex]):focus-visible {
  outline: var(--g-hairline) solid var(--g-focus);
  outline-offset: var(--g-space-1);
}

/* ── sections ───────────────────────────────────────────────────────────── */
section {
  padding: var(--g-space-9) 0;
  border-bottom: var(--g-hairline) solid var(--g-line);
}
.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--g-space-5);
  margin-bottom: var(--g-space-6);
  flex-wrap: wrap;
}
.spine {
  background-color: var(--g-ink-800);
}

/* ── the index: a ruled list, not a card grid ───────────────────────────── */
.index {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: var(--g-hairline) solid var(--g-line);
}
.index > li {
  border-bottom: var(--g-hairline) solid var(--g-line);
}
.index a,
.index .norow {
  display: grid;
  grid-template-columns: 3.5rem 1fr auto;
  gap: var(--g-space-5);
  align-items: baseline;
  padding: var(--g-space-4) var(--g-space-2);
  text-decoration: none;
  transition: background-color var(--g-dur-micro) var(--g-ease-out);
}
.index a:hover {
  background-color: var(--g-ink-700);
}
.index .ord {
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  color: var(--g-text-faint);
  letter-spacing: var(--g-track-label);
}
.index .name {
  font-family: var(--g-face-display);
  font-weight: var(--g-weight-strong);
  font-size: var(--g-size-h3);
  letter-spacing: var(--g-track-display);
}
.index .state {
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  letter-spacing: var(--g-track-label);
  text-transform: uppercase;
  color: var(--g-text-dim);
}
.index .state.absent {
  color: var(--g-absent);
}

/* ── the provenance block. RISK 2 lives here. ───────────────────────────── */
.provenance {
  border-left: var(--g-space-1) solid var(--g-accent);
  padding-left: var(--g-space-5);
  margin: var(--g-space-6) 0;
}
.provenance .authority {
  /* Same tier as an h3, deliberately. This is the risk. */
  font-family: var(--g-face-display);
  font-size: var(--g-size-h3);
  font-weight: var(--g-weight-strong);
  letter-spacing: var(--g-track-display);
  line-height: var(--g-leading-tight);
  color: var(--g-text);
  margin-bottom: var(--g-space-4);
  max-width: var(--g-measure-wide);
}
.facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--g-space-2) var(--g-space-5);
  margin: 0;
  font-size: var(--g-size-small);
}
.facts dt {
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  letter-spacing: var(--g-track-label);
  text-transform: uppercase;
  color: var(--g-text-faint);
  padding-top: var(--g-space-1);
}
.facts dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* ── editorial: visually distinct, labelled historical background (§A) ──── */
.editorial {
  background-color: var(--g-ink-700);
  border: var(--g-hairline) solid var(--g-line);
  border-radius: var(--g-radius);
  padding: var(--g-space-6);
  margin: var(--g-space-7) 0;
}
.editorial > .label {
  color: var(--g-accent-bright);
}
.editorial .sources {
  margin: var(--g-space-5) 0 0;
  padding-top: var(--g-space-4);
  border-top: var(--g-hairline) solid var(--g-line);
  font-size: var(--g-size-small);
  color: var(--g-text-dim);
  list-style: none;
}
.editorial .sources li {
  margin-bottom: var(--g-space-2);
  /* Source citations carry unbroken URLs; without this they overflow 375px. */
  overflow-wrap: anywhere;
}

/* ── absent states, designed rather than apologised for (G10) ───────────── */
.absent-state {
  border: var(--g-hairline) dashed var(--g-absent);
  border-radius: var(--g-radius);
  padding: var(--g-space-7) var(--g-space-6);
  color: var(--g-text-dim);
  background-color: var(--g-ink-800);
}
.absent-state .label {
  color: var(--g-absent);
}
.disabled-surface {
  border-left: var(--g-space-1) solid var(--g-absent);
  padding-left: var(--g-space-5);
}

/* ── viewer surface on a machine page ───────────────────────────────────── */
.stage {
  position: relative;
  aspect-ratio: 16 / 9;
  background-color: var(--g-ink-800);
  border: var(--g-hairline) solid var(--g-line);
  border-radius: var(--g-radius);
  overflow: hidden;
}
.stage canvas {
  width: 100%;
  height: 100%;
}
.viewer-controls {
  display: flex;
  gap: var(--g-space-4);
  align-items: center;
  flex-wrap: wrap;
  margin-top: var(--g-space-4);
}
/* D2 — the section control's honesty copy, its own block below the controls. */
.clip-honesty {
  margin-top: var(--g-space-2);
  max-width: var(--g-measure);
}
.stills {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
  gap: var(--g-space-4);
  list-style: none;
  margin: var(--g-space-5) 0 0;
  padding: 0;
}

footer {
  padding: var(--g-space-8) 0 var(--g-space-9);
  color: var(--g-text-dim);
  font-size: var(--g-size-small);
}
footer .disclaimer {
  border-top: var(--g-hairline) solid var(--g-line);
  padding-top: var(--g-space-5);
  max-width: var(--g-measure-wide);
}

/* ── entrance motion (G6): 720ms total, 60ms stagger, ease-out ──────────── *
 * Applied ONLY under .js — with JavaScript disabled the class is never set
 * and every element is at its final state in the initial HTML.                */
.js [data-enter] {
  opacity: 0;
  transform: translateY(var(--g-space-3));
}
.js [data-enter].shown {
  opacity: 1;
  transform: none;
  transition:
    opacity var(--g-dur-entrance) var(--g-ease-out),
    transform var(--g-dur-entrance) var(--g-ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .js [data-enter] {
    opacity: 1;
    transform: none;
  }
  .js [data-enter].shown {
    transition: none;
  }
}

/* ── responsive: 375 / 768 / 1440 ───────────────────────────────────────── */
@media (min-width: 48rem) {
  h1 {
    font-size: var(--g-size-display);
  }
  .wrap {
    padding: 0 var(--g-space-8);
  }
}
@media (max-width: 47.99rem) {
  .rail {
    top: auto;
    right: 0;
    bottom: 0;
    width: auto;
    height: var(--g-rail);
    flex-direction: row;
    border-right: 0;
    border-top: var(--g-hairline) solid var(--g-line);
    padding: 0 var(--g-space-4);
  }
  .rail nav {
    /* Four labels do not fit across 375px. Scrolling keeps every collection
       reachable instead of clipping the last one at the viewport edge. */
    overflow-x: auto;
    max-width: 100%;
  }
  .rail nav ol {
    flex-direction: row;
    gap: var(--g-space-4);
    flex-wrap: nowrap;
  }
  .rail nav a {
    white-space: nowrap;
  }
  /* The vertical writing mode lives on the ANCHOR at desktop width, so it must
     be reset there too — resetting only the list left every mobile nav label
     rotated on its side and overlapping its neighbours. */
  .rail nav a {
    writing-mode: horizontal-tb;
    transform: none;
  }
  .rail .count {
    writing-mode: horizontal-tb;
  }
  main {
    margin-left: 0;
    padding-bottom: var(--g-rail);
  }
  /* The rail is a FIXED bar at the bottom on mobile, so a full-viewport hero
     sits underneath it and its last line — the counts — was covered. The hero
     gives back exactly the rail's height. */
  .hero {
    min-height: calc(100svh - var(--g-rail));
  }
  .index a,
  .index .norow {
    grid-template-columns: 2.5rem 1fr;
  }
  .index .state {
    grid-column: 2;
  }
  .facts {
    grid-template-columns: 1fr;
    gap: var(--g-space-1) 0;
  }
  .facts dd {
    margin-bottom: var(--g-space-3);
  }
}

/* Interactive targets ≥24px in every direction (1D-19). */
a,
button,
[tabindex] {
  min-height: var(--g-space-6);
}
.index a,
.rail nav a,
.cta {
  min-height: var(--g-space-6);
}

/* ── the skip link. Visible on focus, never a 0×0 trap. ─────────────────── */
.skip {
  position: absolute;
  left: var(--g-space-4);
  top: var(--g-space-4);
  z-index: 9;
  padding: var(--g-space-3) var(--g-space-4);
  background-color: var(--g-ink-600);
  border: var(--g-hairline) solid var(--g-accent);
  border-radius: var(--g-radius);
  color: var(--g-accent-bright);
  font-family: var(--g-face-mono);
  font-size: var(--g-size-label);
  letter-spacing: var(--g-track-label);
  text-transform: uppercase;
  text-decoration: none;
  transform: translateY(-200%);
}
.skip:focus {
  transform: none;
}

/* The hero standfirst measure is wider than body prose: it is a statement,
   not a paragraph, and wrapping it at 34rem breaks the line rhythm. */
.hero .standfirst,
.standfirst {
  max-width: var(--g-measure-wide);
  font-size: var(--g-size-h3);
  font-weight: var(--g-weight-body);
  line-height: var(--g-leading-body);
}

/* A range input inside a mono label needs the label to lay out horizontally. */
.viewer-controls label {
  display: inline-flex;
  align-items: center;
  gap: var(--g-space-3);
}

/* ═══════════════════════════════════════════════════════════════════════════
 * 1E — ART DIRECTION
 *
 * Elevation WITHIN the token system. Colour, type scale and radius are settled
 * in tokens/base.css and are not re-litigated here: every value below is a
 * var() reference, and 1E-21 fails a signature move whose definition carries a
 * literal style value.
 *
 * The five signature moves are declared with their ids in out/BRIEF-1E.md's
 * front matter. Each one's selector must appear in the built HTML for the
 * routes it names.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── MOVE `drafting-rule` ───────────────────────────────────────────────────
 * The hairline that separates apparatus from subject. On every route, once.
 * It scales from the left rather than fading: a rule that fades in reads as
 * decoration, a rule that draws reads as a drawing being made. */
.drafting-rule {
  border: 0;
  height: var(--g-hairline);
  margin: 0 0 var(--g-space-5) 0;
  background: linear-gradient(to right, var(--g-accent) 0%, var(--g-line) 40%, transparent 100%);
  transform-origin: left center;
  transform: scaleX(0);
  transition: transform var(--g-dur-entrance) var(--g-ease-out);
}
.js .drafting-rule { transform: scaleX(1); }
/* No-JS: `.js` is never set, so the rule would stay at scaleX(0) and vanish.
   The static state must be the FINAL state, never the pre-animation one. */
html:not(.js) .drafting-rule { transform: scaleX(1); }

/* ── MOVE `stage-vignette` ──────────────────────────────────────────────────
 * base.css already argues that the canvas is the brightest thing on any page.
 * §B's job is to make the page agree: the stage carries a graphite ramp inward
 * from its edges so the object sits in the one pool of light.
 *
 * This is G11 RISK 1. The chrome is deliberately darker than a conventional
 * content page, which is wrong if it reads as an unfinished dark theme. */
.stage {
  position: relative;
  aspect-ratio: var(--g-stage-ratio);
  background:
    radial-gradient(ellipse at center, var(--g-ink-700) 0%, var(--g-ink-900) var(--g-vignette-stop), var(--g-ink-900) 100%);
  border: var(--g-hairline) solid var(--g-line);
  border-radius: var(--g-radius);
  overflow: hidden;
}
.stage canvas { display: block; width: 100%; height: 100%; }

/* ── MOVE `absence-keyline` ─────────────────────────────────────────────────
 * G11 RISK 2. 39 of 42 routes have no viewer. Demoting them would make the
 * site look 93% broken, so an absence gets the SAME measure and type weight as
 * a viewer page and is marked by a keyline in --g-absent rather than by being
 * dimmed and shrunk. base.css defines --g-absent as "the colour of a thing
 * that is not available"; an absence a visitor cannot read is not an honest
 * absence. */
.absent-state {
  /* The 1D rule at the top of this file draws a DASHED box on an --g-ink-800
     fill. A dashed border is the universal idiom for provisional / placeholder /
     unfinished — exactly the register this move exists to escape — so leaving it
     in place stated the honest-absence argument in prose and denied it in CSS on
     39 of 42 routes. It is reset here, not overridden piecemeal. */
  border: 0;
  border-left: var(--g-tick) solid var(--g-absent);
  border-radius: 0;
  padding: var(--g-space-3) 0 var(--g-space-3) var(--g-space-4);
  background-color: transparent;
  max-width: var(--g-measure);
}
.absent-state .label { color: var(--g-absent); letter-spacing: var(--g-track-label); }

/* SURVIVAL FLOOR. #viewer-fallback shares .absent-state but lives INSIDE
   .stage, which is aspect-ratio-boxed with overflow:hidden. In normal flow, and
   with the max-width above making it taller, the poster, the machine name, the
   authority sentence and the licence/author lines were clipped away at 375px
   for any visitor with no JS or no WebGL — an attribution-visibility failure on
   the CC-licensed rows, not merely a layout one. It is taken out of flow and
   allowed to scroll, matching what .hero-stage already does. */
/* NOT scrollable. `overflow: auto` here fixed the clipping but introduced a
   serious axe violation — scrollable-region-focusable: a scroll container with
   no keyboard-focusable child cannot be reached by keyboard at all. Caught by
   re-running gate:1d (1D-18) at the final HEAD.
   
   The right fix is to remove the scroll region rather than add a tabindex to
   satisfy the rule: when the fallback is showing there is no canvas to size the
   box for, so the stage simply stops being an aspect-ratio box and grows to fit
   its content. Nothing is clipped and nothing needs scrolling. */
.stage:has(#viewer-fallback:not([hidden])) {
  aspect-ratio: auto;
  overflow: visible;
  min-height: 0;
}
.stage #viewer-fallback {
  position: static;
  max-width: none;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--g-space-2);
  padding: var(--g-space-4);
  background-color: var(--g-ink-900);
}
.stage #viewer-fallback img { max-width: 100%; height: auto; }

/* The two rules above (and the .hero-stage pair) each declare `display`, which
   outranks the user-agent's `[hidden] { display: none }`. So when the viewer
   mounted and set `fallback.hidden = true`, the poster block kept its box and
   the live canvas was clipped away by the stage's aspect-ratio/overflow box —
   the 3D view was never on screen on any viewer-bearing page. The attribute is
   the only signal the viewer took over, and it has to win. Both selectors are
   named so this holds regardless of source order. */
.stage #viewer-fallback[hidden],
.hero-stage #viewer-fallback[hidden] {
  display: none;
}

/* ── MOVE `index-stagger` ───────────────────────────────────────────────────
 * 1D's own report recorded the collection pages as the most template-shaped
 * surfaces in the build. This is the fix: a ruled index whose rows
 * (no quality adjective here: 1C-26 governs quality claims and a CSS comment is
 * not exempt from that closed world — it caught one in this very block)
 * rise one --g-stagger apart, with the ordinal set in the mono face so the
 * column reads as a plate list rather than a nav menu. */
.index-row {
  border-bottom: var(--g-hairline) solid var(--g-line);
}
/* Scoped under .js, matching 1D's [data-enter] discipline. Unscoped, a
   choreograph() failure (no IntersectionObserver, or any throw before the
   observe loop) left .js already set, the html:not(.js) rescue inapplicable,
   and up to 17 collection rows permanently invisible. The initial state of an
   entrance must never be reachable without the code that ends it. */
html.js .index-row {
  opacity: 0;
  transform: translateY(var(--g-space-2));
  transition: opacity var(--g-dur-entrance) var(--g-ease-out), transform var(--g-dur-entrance) var(--g-ease-out);
}
.index-row.shown { opacity: 1; transform: none; }
html:not(.js) .index-row { opacity: 1; transform: none; }
.index-row .ord { font-family: var(--g-face-mono); color: var(--g-text-faint); }
.index-row:hover { background: var(--g-ink-800); }

/* ── MOVE `cut-edge` ────────────────────────────────────────────────────────
 * §E. When the section view is active the stage takes the drafting-sheet
 * treatment: the cut is announced by a cool --g-cut keyline, NOT by filling the
 * cut face. A GLB mesh may be an open surface, and a filled face through one
 * invents material the ledger cannot vouch for. There is no dimension caption,
 * because every reconstruction is scaleAuthority "unverified". */
.clipping .stage { box-shadow: inset 0 0 0 var(--g-cut-width) var(--g-cut); }
/* --g-cut is declared `non-text-ui` (3:1) in the token amendment, so it may NOT
   set text. It previously set the pressed control's `color` as well as its
   border, which made the declaration false inside the exact ratio-and-threshold
   machinery built to stop that. It also sat a few degrees of hue from
   --g-focus, contradicting its own stated reason that a cut edge must never
   read as something clickable. Border only; the text keeps the existing
   affordance grammar. */
.clip-control[aria-pressed='true'] { border-color: var(--g-cut); }

/* §C — THE HONESTY CAPTION IS NOT HERE, AND COULD NOT BE.
 *
 * The design critic's blocking finding 1E-C-03: a section cut asserts that the
 * interior is KNOWN, while all three viewable rows are reconstructions of
 * EXTERIOR form only. The fix is a caption stating what the cut actually shows.
 *
 * That caption cannot be added by this session. It is a CLAIM ABOUT A MODEL, so
 * 1D's own furnitureProblems() correctly refuses it as a FURNITURE entry — only
 * the claim vocabulary in src/claims.ts may make a claim about a model — and
 * src/claims.ts is outside 1E's anchored allowed-path list. Shipping it broke
 * 1D-09 twice over: as an illegal furniture entry AND as an authority-bearing
 * string absent from the generated vocabulary.
 *
 * So 1E-C-03 is recorded UNRESOLVED in out/FINDINGS-1E.json, 1E-17 fails on it,
 * and closing it needs a session with authority over the claim vocabulary. The
 * page does still state the reconstruction status in its provenance block; what
 * is missing is that statement tied to the cut itself. */

/* ── §F the guided tour ─────────────────────────────────────────────────────
 * Static-first. With no JS this is a stacked list of prose the visitor reads
 * top to bottom; pinChapter upgrades the same DOM into a pinned sequence. */
.tour { margin-top: var(--g-space-6); max-width: var(--g-measure); }
.tour-beats { position: relative; }
.tour-beat { padding: var(--g-space-3) 0; border-top: var(--g-hairline) solid var(--g-line); }
.tour-beat .label { color: var(--g-accent-bright); }

/* SS-D — the separation chapter, in the tour grammar: ruled beats, label
   track, full measure. Signature move `separation-chapter`. */
.separation { margin-top: var(--g-space-6); max-width: var(--g-measure); }
.separation-beats { position: relative; }
.separation-beat { padding: var(--g-space-3) 0; border-top: var(--g-hairline) solid var(--g-line); }
.separation-beat .label { color: var(--g-accent-bright); }

@media (prefers-reduced-motion: reduce) {
  /* The survival floor: every entrance lands in its final state with no
     transition at all. base.css already zeroes the durations; this makes the
     pre-animation transform unreachable too, so nothing can be left mid-move. */
  .drafting-rule, .index-row { transform: none; opacity: 1; transition: none; }
}
