/* --- fonts.css --- */
/* GENERATED by src/fonts.py - do not edit by hand.
 * Recursive (c) The Recursive Project Authors, SIL Open Font License 1.1.
 * Noto Sans Georgian (c) The Noto Project Authors, SIL Open Font License 1.1.
 * FiraGO (c) Carrois Corporate GbR and HERE Europe B.V., SIL Open Font License 1.1.
 * Licence texts and source hashes: src/assets/fonts/vendor/ and src/fonts.py.
 * What was measured, and why this Georgian face: src/assets/fonts/measurement.json.
 */

@font-face {
  font-family: 'Recursive';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/recursive-latin.71c055d4fb.woff2') format('woff2');
  unicode-range: U+000D,U+0020-007E,U+00A0-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2007-200B,U+2010-2015,U+2018-201A,U+201C-201E,U+2020-2022,U+2026,U+2030,U+2032-2033,U+2039-203A,U+203E,U+2044,U+2052,U+2074,U+20AC,U+2122,U+2190-2193,U+2212,U+2215,U+2260,U+2264;
}

@font-face {
  font-family: 'Noto Sans Georgian';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/noto-sans-georgian-mkhedruli.940cb74bf0.woff2') format('woff2');
  unicode-range: U+10D0-10FF;
  ascent-override: 95%;
  descent-override: 25%;
  line-gap-override: 0%;
}

@font-face {
  font-family: 'FiraGO';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/firago-arabic-indic-digits.7f2b252d57.woff2') format('woff2');
  unicode-range: U+0660-0669;
  ascent-override: 95%;
  descent-override: 25%;
  line-gap-override: 0%;
}

/* --- tokens.css --- */
/* Design tokens — the aluminium instrument (DESIGN-BRIEF.md §5.11, decided by
 * the user on 2026-09-13).
 *
 * The page is an instrument chassis printed in ink. Dark screens are set into it
 * only where the machine speaks — the hero's recorded run, the S5 ledger table,
 * and the command boxes (§5.4, screen inventory) — and the verdict signals exist
 * only inside those screens. There is no brand colour: the brand colour is ink.
 *
 * COLOUR is decided in this file and nowhere else. Every value below was chosen
 * by measurement against every pair the system forms, and tests/test_contrast.py
 * re-measures all of them from this file at run time rather than trusting the
 * numbers written in these comments:
 *
 *   text on the chassis    --ink 11.97:1, --ink-2 7.15:1, --ink-3 4.92:1
 *                          (worst case: each on --chassis-2)
 *   text on a screen       --screen-ink 14.35:1, --screen-ink-2 7.24:1,
 *                          --pass 8.25:1, --refine 8.29:1, --defect 5.98:1
 *                          (worst case: each on --screen-2)
 *   a filled signal        --screen on --pass 9.07:1, on --defect 6.57:1
 *   strokes (1.4.11)       --rule 3.22:1 at worst (on --chassis-2), and 3.50:1
 *                          on --screen-2: one rule tone serves both surfaces
 *   focus, both surfaces   --focus-ring 11.97:1 on --chassis-2, --focus-gap
 *                          14.35:1 on --screen-2, and 15.25:1 between the two
 *
 * A colour literal anywhere else the generator delivers is one of exactly four
 * things:
 *
 *   1. a colour this file declares, at an alpha, because no CSS function takes
 *      an alpha of a hex custom property (a cast in ink, the header's chassis);
 *   2. a colour this file declares, at full opacity, where no custom property
 *      can reach: the web manifest in src/build.py, the theme-color meta in
 *      src/templates/shell.html, and src/assets/favicon.svg;
 *   3. black at an alpha, which is a cast and was never palette;
 *   4. #000 inside a mask's gradient, which is coverage and never a colour.
 *
 * In the stylesheets themselves not even the first kind is written out: every
 * cast and edge at an alpha is a token below (--shadow-raise, --ring-hover,
 * --shadow-bezel), and tests/test_contrast.py fails an rgb() or rgba() in any
 * other file of src/styles/.
 *
 * No glow, no gradient atmosphere, no coloured cast: every shadow is cast in ink
 * or black at an alpha, and an edge drawn with a shadow has no blur and no offset.
 */
:root {
  /* The chassis: brushed aluminium, not off-white. --chassis-2 is the same
     metal recessed (a key's track, a pressed control, a row under the pointer);
     at 1.13:1 it is never a boundary on its own, --rule is. */
  --chassis: #D9DAD6;
  --chassis-2: #CDCEC9;

  /* Ink. Text, strokes, the mark, and the one fill a control on the chassis
     may have. Secondary and tertiary text are the ink thinned toward the
     metal, never a new grey. */
  --ink: #0F1111;
  --ink-2: #383B3A;
  --ink-3: #505351;

  /* The identifying stroke, on the chassis and in a screen alike. Its
     luminance sits where its contrast against the lightest chassis tone and
     the darkest screen tone would meet, which is why one value clears 3:1 on
     all four surfaces. */
  --rule: #6C6F6B;

  /* Screens: set into the chassis, no glow. --screen-2 is an inset within a
     screen (a code line's well, a table's header row). */
  --screen: #0B0C0C;
  --screen-2: #161818;
  --screen-ink: #E6E7E3;
  --screen-ink-2: #A3A6A1;

  /* Verdict signals. SCREENS ONLY — tests/test_contrast.py fails any rule
     outside a screen that names one. Meaning is always carried by a glyph and
     a word as well; the colour is the third channel, never the only one. */
  --pass: #5CC48B;
  --defect: #F26B5E;
  --refine: #E2A640;

  /* Focus, visible on both surfaces: a two-tone indicator. The ring is ink,
     which reads on the chassis; the gap between it and the control is screen
     ink, which reads in a screen. Whichever surface the control sits on, one
     of the two clears 3:1 against it. */
  --focus-ring: var(--ink);
  --focus-gap: var(--screen-ink);

  /* --- Type ----------------------------------------------------------------
     One family. Recursive's MONO axis is the colour rule set in type: Sans
     (MONO 0) on the chassis, where a person reads; Mono (MONO 1) in screens
     and code, where the machine speaks. Both stacks name the same three faces
     in the same order: Recursive draws Latin, Noto Sans Georgian draws
     Mkhedruli (chosen by measurement, src/assets/fonts/measurement.json), and
     FiraGO draws only the ten Arabic-Indic digits of the hero's evidence line.
     The system fallbacks after them differ, which is all that separates the
     two. The axis is set through font-variation-settings, and only MONO:
     CASL, slnt and CRSV are pinned inside the subset, and wght is font-weight. */
  --font-sans: 'Recursive', 'Noto Sans Georgian', 'FiraGO', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'Recursive', 'Noto Sans Georgian', 'FiraGO', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --axis-sans: "MONO" 0;
  --axis-mono: "MONO" 1;

  /* The shipped wght range is 400-700, and these are its four named stops. */
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-bold: 600;
  --weight-display: 700;

  /* The scale: whole pixels, every adjacent step at least 1.25 — 12, 15, 19,
     24, 30, 38, 48, 60, then the poster. Eight steps above a 12px floor force
     the body to 19px: 12 x 1.25 x 1.25 is 18.75. Recursive's measured x-height
     is 543/1000em (the median ink top of a-z at 400), so 19px sets 10.3px.
     --size-hero is the front page's headline where the hero has the room for
     it: measured on 2026-09-14, the Georgian H1 at 48px holds three lines in
     the five-column block from 1440px wide and leaves the copy column above
     the fold from 820px tall, and at 1366x768 it takes a fourth line and
     pushes the column's last link under the fold, so there the H1 keeps
     38px (src/styles/sections.css).
     --size-moment is the page's memorable moment (DESIGN-BRIEF.md §8.1 №1):
     what the gate changed in the recorded lines, `\d` to `[0-9]` and 2700 to
     raise, set larger than the headline at every width it has, because the
     moment was the smallest text in the first viewport. It is one step above
     the hero step and one below the poster, and it is used by the hero screen
     alone.
     Only the poster figure is fluid, because it is the one element wide
     enough to overflow a 320px screen in Georgian; its minimum is 1.25x the
     moment step, so the ratio holds at every width. tests/test_contrast.py
     evaluates the whole scale from 320 to 1920px. */
  --size-micro: 0.75rem;
  --size-small: 0.9375rem;
  --size-body: 1.1875rem;
  --size-h3: 1.5rem;
  --size-h2: 1.875rem;
  --size-h1: 2.375rem;
  --size-hero: 3rem;
  --size-moment: 3.75rem;
  --size-display: clamp(4.6875rem, 1.5rem + 11vw, 6rem);

  /* Leading, from the measured vertical metrics (measurement.json,
     "calibration"). Recursive's content box is 950 + 250 = 1.2em, and the
     tallest ink a line of Latin can hold at 700 is 1.044em, so no step goes
     below that: lines never collide. The headings sit close to it, the body
     carries the reading white, and text at the small step — the screens'
     notes, the lens and gate descriptions, all running text on a phone —
     sits between the two, because its lines are short: at 15px a column of
     the grid or a 390px phone holds 40-60 characters, not the 66 the body
     leading is set for. */
  --lh-display: 1.05;
  --lh-h1: 1.15;
  --lh-h2: 1.2;
  --lh-h3: 1.3;
  --lh-small: 1.4;
  --lh-body: 1.5;

  /* Tracking closes only the display sizes, and never below -0.04em. Zeroed
     for Georgian below: Mkhedruli has no capitals, its counters are the
     letterform. --track-hero is the front page's headline set as display
     (§8.4 №13): at the display weight Recursive's Sans sets wide, so the 38
     and 48px headline closes half a hundredth of an em further than the
     poster, whose size already reads tight. */
  --track-display: -0.02em;
  --track-hero: -0.025em;
  --track-h1: -0.01em;
  --track-h2: -0.005em;

  /* Measure. `ch` is the advance of `0`, which in Recursive is 600/1000em in
     both Sans and Mono. Over the decks as they stood on 2026-09-13 the mean
     advance was 507.9 in English and 598.8 in Georgian, so one `ch` bought
     1.181 English characters and 1.002 Georgian ones: 56ch and 66ch are both
     ~66 characters.
     tests/test_i18n.py recomputes the means from the decks and the recorded
     advances on every run. */
  --measure: 56ch;
  --measure-lead: 47ch;
  --measure-wide: 60ch;

  /* --- Space ---------------------------------------------------------------
     The one spacing scale (§4, §5.11 №3). Named by value: a step is its size. */
  --space-4: 4px;
  --space-8: 8px;
  --space-16: 16px;
  --space-24: 24px;
  --space-32: 32px;
  --space-48: 48px;
  --space-64: 64px;
  --space-96: 96px;

  /* The 12-column grid. Twelve columns at every width; the gutter and the side
     margin are scale steps and widen at the two breakpoints the brief uses —
     720px, where a phone's compact form ends, and 1080px, the wide layout
     shared with src/scripts/site.js, where the stage instrument turns
     horizontal. At 1440 the grid is exactly
     12 x 90 + 11 x 24 = 1344px between 48px margins. */
  --grid-columns: 12;
  --grid-gutter: var(--space-16);
  --grid-margin: var(--space-16);
  --grid-max: 1344px;

  /* Instrument-precise corners: a key, a plate, a screen. */
  --radius-xs: 2px;
  --radius-sm: 4px;
  --radius-md: 8px;

  /* The one cast: something raised off the chassis, lit from above. Ink at an
     alpha, never a tint and never a halo. Only a control lifts off the metal
     (the 404's call to act, the header's drawer); nothing that is read does,
     the skill cards included (§8.3 №11). */
  --shadow-raise: 0 1px 2px rgba(15, 17, 17, .14), 0 8px 24px rgba(15, 17, 17, .10);
  /* The edge an ink button draws under the pointer: the chassis at .5, inset,
     with no blur and no offset. Composited over --ink it measures 4.06:1, so
     it is a real edge and not a tint. */
  --ring-hover: inset 0 0 0 2px rgba(217, 218, 214, .5);
  /* The seat every screen sits in (§8.3 №10). A screen is set INTO the
     chassis, not stood on it: inside its rule-tone lip, a dark rim, and a soft
     cast from the lip's upper edge falling onto the glass. Both layers are
     black at an alpha and both are inset, so nothing is lit, nothing is
     coloured and nothing reaches out over the metal. */
  --shadow-bezel: inset 0 0 0 1px rgba(0, 0, 0, .9), inset 0 8px 12px -6px rgba(0, 0, 0, .9);

  /* --- Motion (§5.6) -------------------------------------------------------
     The durations of the five items, and nothing else moves. №1, the recorded
     run, is a ceiling (--duration-run) over beats that each take
     --duration-beat and start --stagger-run apart within the lens rows. */
  --duration-run: 2400ms;
  --stagger-run: 60ms;
  --duration-beat: 240ms;
  --duration-toggle: 180ms;
  --duration-open: 240ms;
  --duration-close: 160ms;
  --duration-copy: 180ms;
  --duration-hover: 180ms;
  /* Exponential ease-out from an already-visible default: the disclosure and
     the toggle's roll. Colour changes use the standard curve. */
  --ease-decelerate: cubic-bezier(.16, 1, .3, 1);
  --ease-standard: cubic-bezier(.4, 0, .2, 1);
}

@media (min-width: 720px) {
  :root {
    --grid-gutter: var(--space-24);
    --grid-margin: var(--space-32);
  }
}

@media (min-width: 1080px) {
  :root {
    --grid-margin: var(--space-48);
  }
}

/* Georgian calibration, from the same record.
 *
 * No size scale. Noto Sans Georgian's Mkhedruli x-height is 1.006x Recursive's
 * at 400 (1.007x at 500, 1.009x at 600), so the two scripts already stand at
 * one optical size, and a heading scale would only break the 1.25 steps.
 *
 * Leading: 87.9% of Mkhedruli letters leave the x-band against 50% of Latin
 * a-z, and the mean vertical ink per letter is 0.773em against 0.656em. Every
 * leading below adds the difference, rounded up — 0.12em — so the mean white
 * between two lines is the same in both editions.
 *
 * Measure: re-derived for the Georgian `ch`, see --measure above.
 */
:lang(ka) {
  --lh-display: 1.17;
  --lh-h1: 1.27;
  --lh-h2: 1.32;
  --lh-h3: 1.42;
  --lh-small: 1.52;
  --lh-body: 1.62;
  --track-display: 0;
  --track-hero: 0;
  --track-h1: 0;
  --track-h2: 0;
  --measure: 66ch;
  --measure-lead: 55ch;
  --measure-wide: 71ch;
}

/* --- base.css --- */
/* Base: reset, document surfaces, typography, the Georgian rules.
 *
 * The resets below name only elements this generator actually emits. There are
 * no speculative rules for markup the site cannot produce, because a rule for
 * an element that never renders is a rule nobody can check.
 * tests/test_a11y.py asserts that every element named here appears in the
 * built pages.
 */

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

html {
  -webkit-text-size-adjust: 100%;
  /* No smooth scrolling. An in-page jump that glides is motion, and §5.6 lists
     every motion this page has; a scroll animation is not one of them, so an
     anchor lands at once, for every reader.

     Every in-page link on this one-page site lands under the sticky header
     without this. The bar is 64px tall plus its 1px rule (.header-bar
     min-height and .site-header's border), and the rest is breathing room, so
     a heading arrives below the bar instead of behind it. tests/test_a11y.py
     pins both numbers: move one and the offset fails rather than silently
     going stale. max(), because the bar's height is px but its CONTENT is rem:
     at a raised root font size the bar grows and a fixed offset under-shoots
     (WCAG 1.4.4). 5rem is the same 80px. */
  scroll-padding-top: max(80px, 5rem);
  /* Thumb, then track. A themed scrollbar is an authored control, so the thumb
     owes the same 3:1 as every other boundary: --rule on the recessed
     --chassis-2 track. */
  scrollbar-color: var(--rule) var(--chassis-2);
  scrollbar-width: thin;
}

body {
  margin: 0;
  background: var(--chassis);
  color: var(--ink);
  /* Sans: MONO 0. Every rule that switches to --font-mono switches the axis
     with it, or Recursive would draw "mono" text proportionally;
     tests/test_assets.py holds the two together. */
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  font-size: var(--size-body);
  line-height: var(--lh-body);
  font-weight: var(--weight-regular);
  font-kerning: normal;
  caret-color: var(--ink);
  accent-color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

::selection { background: var(--ink); color: var(--chassis); }

::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--chassis-2); }
::-webkit-scrollbar-thumb {
  background: var(--rule);
  border: 3px solid var(--chassis-2);
  border-radius: var(--radius-sm);
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-3); }

/* Two tones, so the indicator reads on either surface: the ink ring against
   the chassis, the --focus-gap between ring and control against a screen. */
:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px var(--focus-gap);
  border-radius: var(--radius-xs);
}
/* Only suppress the ring for pointer focus, never for keyboard focus. */
:focus:not(:focus-visible) { outline: none; }

::marker { color: var(--ink-2); }

h1, h2, h3, h4, p, ul, ol, dl, dd, figure { margin: 0; }
ul, ol { padding: 0; }

h1, h2, h3, h4 { font-weight: var(--weight-bold); text-wrap: balance; }

h1 {
  font-size: var(--size-h1);
  line-height: var(--lh-h1);
  letter-spacing: var(--track-h1);
}
h2 {
  font-size: var(--size-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--track-h2);
}
h3 {
  font-size: var(--size-h3);
  line-height: var(--lh-h3);
}
/* The page's fourth level is a label inside a block — a lens, a findings
   list — and never a reading heading, so it sits at the body size. */
h4 {
  font-size: var(--size-body);
  line-height: var(--lh-h3);
}

/* Running prose: a measure, no orphan on the last line, and quotation marks
   hung into the margin where the engine can (hanging-punctuation is Safari's;
   elsewhere the line starts on its mark, as it did). */
p {
  max-width: var(--measure);
  text-wrap: pretty;
  hanging-punctuation: first allow-end last;
}

a {
  color: inherit;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}
a:hover { text-decoration-thickness: 2px; }

code {
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  /* `none`, not `font-feature-settings: 'liga' 0`: `none` also turns off
     `calt` and every other contextual ligature a face may drive, so `--` in an
     install command always renders as the two hyphens a reader has to type. */
  font-variant-ligatures: none;
}

/* Figures — counts, versions, severities, dates, the ledger — must not
   jitter. Recursive's default figures are already tabular in both Sans and
   Mono (every digit advances 600/1000em, measurement.json), so this states the
   intent rather than switching anything on. */
time, .count, .version, .num { font-variant-numeric: tabular-nums; }

svg { display: block; }

table { border-collapse: collapse; }
caption { text-align: start; }

/* --- Georgian ----------------------------------------------------------------
 * The calibration itself — leading and measure, re-derived for Recursive and
 * Noto Sans Georgian — lives with the tokens it restates, in
 * src/styles/tokens.css. What stays here is the rule that is not a token:
 * Mkhedruli has no capitals, so Latin tracking closes the counters that ARE the
 * letterform, and every Georgian run sets at zero tracking.
 *
 * text-transform: uppercase appears NOWHERE in this stylesheet, by rule and by
 * test (tests/test_security.py). The shipped Georgian subset is Mkhedruli only,
 * so an uppercase transform would map into Mtavruli, which no shipped face
 * declares, and drop out of the webfont mid-word.
 */
:lang(ka) { letter-spacing: 0; }
h1:lang(ka), h2:lang(ka), h3:lang(ka) { letter-spacing: 0; }

/* --- Layout ------------------------------------------------------------- */
/* The grid's outer frame: its side margin and maximum width are tokens and
   change at the grid breakpoints. */
.container { width: min(100% - 2 * var(--grid-margin), var(--grid-max)); margin-inline: auto; }

.page-shell { background: var(--chassis); min-height: 100dvh; }

.lead {
  font-size: var(--size-body);
  color: var(--ink-2);
  max-width: var(--measure-lead);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  left: var(--space-16);
  top: var(--space-8);
  z-index: 100;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  transform: translateY(-200%);
  background: var(--ink);
  color: var(--chassis);
  padding: var(--space-8) var(--space-24);
  border-radius: var(--radius-xs);
  font-weight: var(--weight-bold);
  text-decoration: none;
}
.skip-link:focus-visible { transform: translateY(0); }

/* No ornament. Empty space is the ornament, and the sections' own padding is
 * what draws it. tests/test_contrast.py proves no repeating pattern and no
 * gradient came back.
 */

/* --- components.css --- */
/* Components. Each container earns its shape from what it holds; there is no
 * single card template repeated down the page.
 *
 * Motion lives in one place per component: its own
 * `@media (prefers-reduced-motion: no-preference)` block, and only the items of
 * DESIGN-BRIEF.md §5.6 exist. A reader who asked for reduced motion gets every
 * state change at once. tests/test_demonstration.py::MotionGrammarTests reads
 * every transition and keyframe on the page against that list. */

/* --- Buttons -------------------------------------------------------------
 * Every control is a real <button> or <a href>. Minimum target 44px in both
 * axes, including the icon-only copy buttons. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  min-width: 44px;
  min-height: 44px;
  padding: var(--space-8) var(--space-24);
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  font: inherit;
  font-size: var(--size-small);
  font-weight: var(--weight-bold);
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}
.btn .icon { flex: none; }

/* The one fill a control on the chassis has: ink. There is no accent colour. */
.btn--primary { background: var(--ink); color: var(--chassis); }
/* A cast cannot light anything on this page, so the hover is an edge on the
   button's own ink (--ring-hover), and a raised button is pressed flush by it:
   the rule below outranks the cast. */
.btn--primary:hover { box-shadow: var(--ring-hover); }
/* Raised off the chassis by the one cast: the 404's single call to act. Every
   cast is ink or black at an alpha, so it darkens the metal and never tints it
   (tests/test_contrast.py::ShadowTests). */
.btn--raised { box-shadow: var(--shadow-raise); }

/* Touch never fires :hover, so without an :active the primary control on the
   page gives no feedback at all until the target view changes. */
.btn:active { transform: translateY(1px); }

/* --- Icons --------------------------------------------------------------- */
.icon { width: 24px; height: 24px; stroke-width: 1.5; }
.icon--sm { width: 18px; height: 18px; }
.icon--xs { width: 14px; height: 14px; }

/* --- Chips and tags ------------------------------------------------------ */
.chip-row { display: flex; flex-wrap: wrap; gap: var(--space-8); padding: 0; margin: 0; list-style: none; }

.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-4);
  border-radius: var(--radius-xs);
  border: 1px solid var(--rule);
  font-size: var(--size-micro);
  font-weight: var(--weight-medium);
  line-height: var(--lh-body);
  color: var(--ink-2);
  white-space: nowrap;
}
/* Conditional: unarmed, a dashed outline rather than a new colour. */
.tag--conditional { border-style: dashed; border-color: var(--ink); color: var(--ink); }

/* --- Command blocks ------------------------------------------------------
 * A terminal is a screen (§5.4, screen inventory, item 3): set into the
 * chassis like the other two, and never lit. It carries no signal colour at
 * all, its copy button's outcome included (§8.5 №17). */
.cmd {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
  background: var(--screen);
  color: var(--screen-ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-bezel);
  padding: var(--space-8) var(--space-4) var(--space-8) var(--space-16);
}
.cmd__text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-8);
  overflow-x: auto;
  padding-block: var(--space-8);
}
.cmd__prompt {
  flex: none;
  color: var(--screen-ink-2);
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-small);
  line-height: var(--lh-small);
  user-select: none;
}
.cmd code {
  font-size: var(--size-small);
  line-height: var(--lh-h3);
  white-space: pre;
  color: var(--screen-ink);
}
.cmd--wrap code { white-space: pre-wrap; overflow-wrap: anywhere; }
/* A wrapped command breaks at a space, and after a /, an @ or a : where the
   markup places a <wbr>; a hyphenated name or a flag is one word and never
   splits (§8.1 №3). A piece wider than the whole box still breaks, as a last
   resort. */
.cmd__word { white-space: nowrap; }
/* The contract fields inside the illustrative command: an underline in the
   screen's own secondary ink, so each annotation below points at a part of
   the line a reader can find. */
.cmd__field {
  text-decoration: underline;
  text-decoration-color: var(--screen-ink-2);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.25em;
}
/* On a phone every command wraps. Unwrapped, the install and clone commands
   were cut off at the box's edge with no ellipsis at 390px, so a reader could
   copy a command they could not read. The copy button takes the text from the
   element, so a visual line break never reaches the clipboard. */
@media (max-width: 599px) {
  .cmd code { white-space: pre-wrap; overflow-wrap: anywhere; }
  .cmd__text { padding-block: var(--space-4); }
}

.copy-btn {
  flex: none;
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* No fill at rest: the screen's own glass, bezel cast included, shows
     through, and a fill would draw a square over the cast. */
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  color: var(--screen-ink-2);
  cursor: pointer;
}
.copy-btn:hover { color: var(--screen-ink); background: var(--screen-2); border-color: var(--rule); }
.copy-btn:active { background: var(--screen-2); transform: translateY(1px); }
/* The result is a different shape, not a different hue. Both outcome glyphs
   ship in the markup and exactly one is ever displayed, so the un-enhanced
   button shows the clipboard and nothing else. Either outcome draws in the
   screen's own full ink with an edge of it: a command box holds no signal
   colour (§5.4, screen inventory), and a green or red here was a second light
   in the hero beside the outcome line (§8.5 №17). The live region says which
   outcome it was. */
.copy-btn__done, .copy-btn__failed { display: none; }
.copy-btn.is-done { color: var(--screen-ink); border-color: var(--screen-ink); }
.copy-btn.is-done .copy-btn__idle { display: none; }
.copy-btn.is-done .copy-btn__done { display: block; }
.copy-btn.is-failed { color: var(--screen-ink); border-color: var(--screen-ink); }
.copy-btn.is-failed .copy-btn__idle { display: none; }
.copy-btn.is-failed .copy-btn__failed { display: block; }

/* --- Links --------------------------------------------------------------- */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  min-height: 44px;
  color: var(--ink);
  font-weight: var(--weight-medium);
  text-decoration-color: var(--rule);
}
.link-arrow:hover { color: var(--ink); text-decoration-color: currentColor; }

/* --- Motion (§5.6 items 4 and 5) -------------------------------------------
 * №4: the copy button's outcome glyph arrives rather than swapping in — the
 * button is small and the reader's eye is on the command, so the change of
 * shape needs a moment to be noticed. №5: a control's colour and edge settle
 * over the same 180ms when the pointer or the focus reaches it. */
@media (prefers-reduced-motion: no-preference) {
  .btn, .copy-btn, .link-arrow {
    transition: background-color var(--duration-hover) var(--ease-standard),
      border-color var(--duration-hover) var(--ease-standard),
      box-shadow var(--duration-hover) var(--ease-standard),
      color var(--duration-hover) var(--ease-standard),
      text-decoration-color var(--duration-hover) var(--ease-standard);
  }
  .copy-btn.is-done .copy-btn__done,
  .copy-btn.is-failed .copy-btn__failed { animation: copy-outcome var(--duration-copy) var(--ease-decelerate) both; }
  @keyframes copy-outcome {
    from { opacity: 0; transform: scale(.6); }
    to { opacity: 1; transform: none; }
  }
}

/* A run of text that must never break across lines: a hyphenated product name
   in prose, where a browser would otherwise break after the hyphen and leave
   two words that name nothing (sections.whole_name). */
.nobreak { white-space: nowrap; }

/* --- sections.css --- */
/* Section layouts, top to bottom (DESIGN-BRIEF.md §5.3-§5.4).
 *
 * One grid: twelve columns, the gutter and margin from tokens.css. Below the
 * wide layout (1080px) a section is one column in document order, which is
 * also its reading and focus order. At the wide layout the page keeps one
 * asymmetry throughout — a five-column block, an empty column, a six-column
 * block — first in the hero, then in every section head.
 *
 * Screens (dark, set into the chassis) exist in three places only: the hero's
 * recorded run (.run, and the toggle inside it), the ledger (.ledger), and the
 * command boxes (.cmd, in components.css). Everything else is ink on metal.
 */

/* --- A phone ---------------------------------------------------------------- */
/* Below 720px the page is one column of the same copy, and at the desktop sizes
   it ran to half as long again as the phone budget allows (§5.3: 15,000px at
   390). Running text steps down one place on the scale, to 15px, and every
   heading with it; Recursive's x-height is 0.543em, so 15px still sets an
   8.1px x-height, and the Georgian face matches it (measurement.json). */
@media (max-width: 719px) {
  body { font-size: var(--size-small); line-height: var(--lh-small); }
  h1 { font-size: var(--size-h2); line-height: var(--lh-h2); }
  h2 { font-size: var(--size-h3); line-height: var(--lh-h3); }
  h3 { font-size: var(--size-body); line-height: var(--lh-h3); }
  .lead { font-size: var(--size-small); line-height: var(--lh-small); }
}

/* --- S0. Header ------------------------------------------------------------ */
/* A flush strip of the chassis with a rule under it: the instrument's top
   panel. Opaque, because text scrolls under it; no blur, no cast. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--chassis);
  border-bottom: 1px solid var(--rule);
}
.header-bar {
  position: relative;
  display: flex;
  /* Wraps only with the script blocked below 1080px, where the four anchors
     are a second row of the bar (§5.10). With the script they are a drawer
     and the bar is one row at every width. */
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--space-24);
  min-height: 64px;
}
/* A 44px target in both axes at every width (§5.10). With its name shown the
   link is far wider than that; where the bar hides the name the 24px mark
   alone would leave a 24px-wide target, so the box keeps 44px and the mark
   sits centred in it. */
.wordmark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-8);
  min-width: 44px;
  min-height: 44px;
  font-weight: var(--weight-bold);
  font-size: var(--size-small);
  line-height: var(--lh-small);
  text-decoration: none;
  color: var(--ink);
  white-space: nowrap;
}
/* 60 by 24px: the mark (A-O, src/mark.py) is drawn on a 24-unit grid, sixty
   units wide, so it renders at one unit per pixel; the width is the mark's
   own and tests/test_assets.py holds the two to each other. */
.wordmark__mark { width: 60px; height: 24px; flex: none; color: var(--ink); }
/* The mark's first half — the A and its bar up to the gate, 29 units — for the
   one band below, where the Georgian bar has no room for the ring. */
.wordmark__mark--compact { display: none; width: 29px; }
/* The display form (src/mark.py), three times the header's size: 180px wide,
   its height from its own viewBox, which is a quarter unit taller above and
   below for the ring's overshoot. The name stands under it, at the size the
   header sets it, so the footer's brand is the mark first and the name second. */
.wordmark--display { flex-direction: column; align-items: flex-start; gap: var(--space-16); }
.wordmark__mark--display { width: 180px; height: auto; }
.wordmark:hover { color: var(--ink-2); }

.nav { margin-left: auto; }
.nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav__link {
  display: inline-flex;
  align-items: center;
  /* A label never breaks mid-word inside the bar: wrapping here would grow the
     sticky header's height, and the anchor offset is derived from a fixed one. */
  white-space: nowrap;
  min-width: 44px;
  min-height: 44px;
  padding-inline: var(--space-8);
  border-radius: var(--radius-xs);
  color: var(--ink-2);
  font-size: var(--size-small);
  line-height: var(--lh-small);
  font-weight: var(--weight-medium);
  text-decoration: none;
}
.nav__link:hover { color: var(--ink); background: var(--chassis-2); }
/* "You are here" (§5.4 S0): site.js sets aria-current="location" on the anchor
   whose section is on screen. Ink, and an ink rule under the label — an edge
   with no blur — so the state is a shape as well as a tone. */
.nav__link[aria-current='location'] { color: var(--ink); box-shadow: inset 0 -2px 0 var(--ink); }
/* Georgian labels are wider, so they keep the tighter padding. */
:lang(ka) .nav__link { padding-inline: var(--space-4); }

.header-actions { display: flex; align-items: center; gap: var(--space-8); }
.header-cta { padding-inline: var(--space-16); }

/* The language switch: a recessed track holding two keys. The language you are
   reading is the filled key and is not a link; the other key is. Each is set in
   its own script, at the same size. */
.lang {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  background: var(--chassis-2);
}
.lang__key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding-inline: var(--space-8);
  border-radius: var(--radius-sm);
  font-size: var(--size-small);
  font-weight: var(--weight-bold);
  line-height: 1;
  letter-spacing: 0.06em;
}
.lang__key:lang(ka) { letter-spacing: 0; }
.lang__key--current {
  color: var(--ink);
  background: var(--chassis);
  /* The track shows around the filled key, so the current language reads as a
     key pressed into the track rather than a chip laid on top of it. */
  box-shadow: inset 0 0 0 4px var(--chassis-2);
}
.lang-switch { min-height: 44px; color: var(--ink-2); text-decoration: none; }
.lang-switch:hover { color: var(--ink); }
.lang-switch:active { background: var(--chassis); }
.lang__key:focus-visible { border-radius: var(--radius-sm); }

/* The repository link: the GitHub mark, the star count and a star, in the same
   recessed track and at the same 44px height as the language switch. */
.gh {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  min-width: 44px;
  min-height: 44px;
  padding-inline: var(--space-8);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  background: var(--chassis-2);
  color: var(--ink-2);
  font-size: var(--size-small);
  font-weight: var(--weight-bold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}
.gh:hover { color: var(--ink); }
.gh:active { background: var(--chassis); }
.gh:focus-visible { border-radius: var(--radius-sm); }
.gh__mark { width: 18px; height: 18px; flex: none; }
.gh__star { width: 12px; height: 12px; flex: none; }

.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  background: var(--chassis-2);
  border: 1px solid var(--rule);
  border-radius: var(--radius-xs);
  color: var(--ink);
  cursor: pointer;
}
/* The markup ships the toggle hidden and site.js unhides it: without the
   script it is a control that does nothing. This has to be explicit, because
   the class rule below sets a display and would otherwise beat [hidden]. */
.nav-toggle[hidden] { display: none; }
.nav-toggle__close { display: none; }
.nav-toggle[aria-expanded='true'] .nav-toggle__open { display: none; }
.nav-toggle[aria-expanded='true'] .nav-toggle__close { display: block; }

/* 1080px is the one layout invariant this stylesheet shares with a script:
   below it the nav is a drawer, above it the four anchors sit in the bar.
   src/scripts/site.js matches the same width to decide when to close the
   drawer, and tests/test_assets.py asserts the literal is in both built
   bundles. Change one, change the other. */
@media (max-width: 1079px) {
  .header-bar { column-gap: var(--space-8); }
  .nav-toggle { display: inline-flex; }
  /* Un-enhanced, the nav is a second row of the bar: visible, in flow, and the
     only route to the four header anchors at this width. site.js lifts it into
     the drawer and collapses it. No `order` anywhere in the header: visual
     order and focus order agree in both states (WCAG 2.4.3). */
  .header-actions { margin-left: auto; }
  .nav {
    flex-basis: 100%;
    margin-left: 0;
    padding-bottom: var(--space-8);
  }
  /* The drawer opens and closes at once. It is not one of §5.6's five
     motions, so it has none. */
  .nav.is-drawer {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    padding: var(--space-8);
    background: var(--chassis);
    border: 1px solid var(--rule);
    border-radius: 0 0 var(--radius-sm) var(--radius-sm);
    box-shadow: var(--shadow-raise);
  }
  .nav.is-collapsed { display: none; }
  .nav__list { flex-direction: column; align-items: stretch; gap: 0; }
  .nav__link { min-height: 48px; padding-inline: var(--space-16); }
  .nav__list li + li { border-top: 1px solid var(--rule); }
}

/* Where the bar gives way, measured in Chromium with the shipped faces every
   10px from 320 to 1600px in both languages (tests/test_a11y.py pins the
   bands), so the widths do not depend on the reader's system fonts. The mark,
   the language switch, the Install button and the menu stay at every width
   (§5.4 S0); the repository pill and the wordmark's name give way first, and
   the footer carries the repository link and the name at every width.

   Below 600px the pill and the name go: with both shown the Georgian bar ran
   out of room below 560px and the English one below 530px. */
@media (max-width: 599px) {
  .header-bar .wordmark__text { display: none; }
  .header-bar .gh { display: none; }
}
/* At 320px the Georgian Install label and the three other controls left the
   bar 3px short; the button's side padding gives it back. */
@media (max-width: 359px) {
  .header-cta { padding-inline: var(--space-8); }
}
/* The A-O mark is sixty units wide, and the Georgian bar — whose Install label
   is the widest — has no room for it below 370px (measured every 10px,
   2026-09-17; the English bar holds it down to 320px). In that band the mark
   keeps its first half, cut at the gate, inside the 44px box the old mark had:
   the box gives 16px of its 44 back to the margins, one scale step each side,
   and lays out as the 28px item the bar was measured around. */
@media (max-width: 369px) {
  :lang(ka) .header-bar .wordmark__mark { display: none; }
  :lang(ka) .header-bar .wordmark__mark--compact { display: block; }
  :lang(ka) .header-bar .wordmark { margin-inline: calc(-1 * var(--space-8)); }
}

/* Georgian from 1080px, where the four anchors return to the bar, to 1169px:
   the Georgian labels are the widest thing in it, and with the wordmark's name
   shown the bar still wrapped at 1130px with the old mark and at 1160px with
   A-O. The mark stays. English keeps room at 1080px and needs no band. */
@media (min-width: 1080px) and (max-width: 1169px) {
  :lang(ka) .header-bar .wordmark__text { display: none; }
}

@media (prefers-reduced-motion: no-preference) {
  /* §5.6 №5 for the header's and footer's controls: colour and fill settle
     when the pointer or the focus reaches them. The drawer itself has none. */
  .nav__link, .lang-switch, .gh, .footer__link {
    transition: background-color var(--duration-hover) var(--ease-standard),
      color var(--duration-hover) var(--ease-standard);
  }
}

/* --- Sections: the shared frame ------------------------------------------- */
/* A seam of the chassis between sections, and more space above a heading than
   below it.

   A section's head is its heading and its lead in ONE column at the lead's
   measure, directly above the thing the section is about, at every width
   (§8.4 №12). Until v2.1 the head kept the hero's asymmetry — the heading in
   columns 1-5, the lead in 7-12 — in five sections running, which drew the
   same empty band between them five times: the rhythm of a documentation
   site, set by a template. Now the head is short, and the rhythm belongs to
   each section's own object: the three tiers, the stage instrument, the
   poster and its ledger, the install steps. */
.section {
  padding-block: var(--space-48);
  border-top: 1px solid var(--rule);
}
.section-head { display: grid; align-content: start; gap: var(--space-8); max-width: var(--measure-lead); margin-bottom: var(--space-24); }
.section-head .lead { max-width: var(--measure-lead); }
@media (max-width: 719px) {
  .section { padding-block: var(--space-32); }
  .section-head { margin-bottom: var(--space-16); }
}

@media (min-width: 1080px) {
  .section { padding-block: var(--space-64) var(--space-32); }
  .section-head { gap: var(--space-16); margin-bottom: var(--space-32); }
}

/* --- S1. Hero ---------------------------------------------------------------- */
/* DOM order is H1, screen, commands, host line, link: the 390px order (§5.4 S1).
   At the wide layout every block is a direct child of one grid, so the copy
   takes columns 1-5 and the screen 7-12 without reordering the DOM. The last
   row is 1fr, so the screen's height never spreads the copy column apart. */
.hero { padding-block: var(--space-16) var(--space-32); }
.hero__inner { display: grid; grid-template-columns: minmax(0, 1fr); row-gap: var(--space-24); }
.hero__head, .hero__run, .hero__install, .hero__host, .hero__secondary { min-width: 0; }
/* The page's one headline, set as display (§8.4 №13): the display weight,
   closed to the hero tracking, on the display leading, and balanced. Its size
   stays the heading step the fold allows (below). */
.hero h1 {
  font-weight: var(--weight-display);
  letter-spacing: var(--track-hero);
  line-height: var(--lh-display);
  text-wrap: balance;
}
.hero__lead { margin-top: var(--space-16); color: var(--ink-2); max-width: var(--measure-lead); }
.hero__install { display: grid; gap: var(--space-16); }
.hero__hint { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }
.hero__host, .hero__secondary { font-size: var(--size-small); line-height: var(--lh-small); }
.hero__figcaption { display: grid; gap: var(--space-8); margin-top: var(--space-16); }
.hero__caption { color: var(--ink); max-width: var(--measure-lead); }
.hero__footnote, .hero__source { font-size: var(--size-micro); line-height: var(--lh-small); color: var(--ink-2); max-width: var(--measure-wide); }

/* The headline takes the hero step where the copy column still ends above
   the fold with it (--size-hero, src/styles/tokens.css). */
@media (min-width: 1440px) and (min-height: 820px) {
  .hero h1 { font-size: var(--size-hero); }
}

/* Both install commands on one line from 1280px (§8.1 №3). The longer is 59
   characters, 531px of mono at the running size against a 479px column at
   1280, so the hero's two commands take one step down, to 425px, and the
   box gives back what it can: its left padding halves, the prompt and the
   copy button sit closer, the button keeps its own 44px as the right edge's
   space, and the box reaches across the gutter it borders, toward the empty
   column before the screen. At 1280 that leaves the line 9px of room, and
   tests/test_a11y.py redoes the sum from these declarations. */
@media (min-width: 1280px) {
  .hero__install .cmd {
    align-items: center;
    gap: var(--space-4);
    margin-inline-end: calc(-1 * var(--grid-gutter));
    padding-inline: var(--space-8) 0;
  }
  .hero__install .cmd__text { gap: var(--space-4); }
  .hero__install .cmd code, .hero__install .cmd__prompt { font-size: var(--size-micro); }
  /* 12px is the page's floor, and the primary action is set at it only
     because no larger step holds the line: at 15px the plugin command needs
     526px, and the box holds 434px at 1280 and 501px from 1440, where the
     container stops growing (measured 2026-09-14). Mono advances do not change
     with weight, so the medium weight costs the line nothing and gives the
     12px strokes on the dark glass their full edge. */
  .hero__install .cmd code { font-weight: var(--weight-medium); }
}

@media (min-width: 1080px) {
  .hero__inner {
    grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
    grid-template-rows: auto auto auto 1fr;
    column-gap: var(--grid-gutter);
    row-gap: 0;
    align-items: start;
  }
  .hero__head { grid-column: 1 / span 5; grid-row: 1; }
  .hero__install { grid-column: 1 / span 5; grid-row: 2; margin-top: var(--space-24); }
  .hero__host { grid-column: 1 / span 5; grid-row: 3; margin-top: var(--space-8); }
  .hero__secondary { grid-column: 1 / span 5; grid-row: 4; }
  .hero__run { grid-column: 7 / span 6; grid-row: 1 / span 4; }
}

/* --- The recorded run: a screen -------------------------------------------------
 * The run as it happened, top to bottom, in the machine's voice: Mono. The
 * sentences that explain it are a person's, so they are set in Sans inside
 * the same screen — the MONO axis tells the two voices apart (§5.11 №2). The
 * glyphs are screen ink; the one filled signal is the outcome line (§5.4 S1). */
.run {
  display: grid;
  background: var(--screen);
  color: var(--screen-ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-bezel);
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-small);
  line-height: var(--lh-h3);
  overflow: hidden;
}
.run > * { padding-inline: var(--space-16); }
.run__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  padding-top: var(--space-8);
  font-size: var(--size-micro);
  color: var(--screen-ink-2);
  max-width: none;
}
.run__task { color: var(--screen-ink); max-width: none; }
.run__key {
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  color: var(--screen-ink-2);
}
.run__lenses { padding-block: var(--space-8); }
.run__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  min-height: 44px;
  color: var(--screen-ink);
  cursor: pointer;
  list-style: none;
}
.run__summary::-webkit-details-marker { display: none; }
.run__count {
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
}
.run__tally { display: inline-flex; align-items: center; gap: var(--space-4); }
/* Every visible word in a row is a Latin identifier, in either edition, so the
   rows keep the Latin heading leading: the Georgian increment pads lines of
   Mkhedruli, and none is drawn here (the state's name is visually hidden). */
.run__rows { list-style: none; margin: 0; padding: 0; line-height: 1.3; }
.run__row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 18px;
  align-items: center;
  column-gap: var(--space-16);
  min-height: 20px;
}
.run__lens { color: var(--screen-ink); }
.run__tool { font-size: var(--size-micro); color: var(--screen-ink-2); }
.run__state { display: flex; color: var(--screen-ink); }
.run__fold {
  position: relative;
  padding-block: var(--space-8) var(--space-4);
  border-top: 1px solid var(--rule);
  color: var(--screen-ink);
  max-width: none;
}
.run__pass0 {
  position: relative;
  display: grid;
  gap: 0;
  padding-bottom: var(--space-8);
  color: var(--screen-ink-2);
  max-width: none;
}
.run__sev { color: var(--screen-ink); white-space: pre-wrap; }
.run__pass0-note {
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
}
.run__refine {
  position: relative;
  display: flow-root;
  padding-block: var(--space-8);
  border-top: 1px solid var(--rule);
}
/* REFINE 1/2: an amber hairline, never a fill. It floats at the head of the
   rule it names, so the sentence runs on under it at the screen's full width
   instead of in a column beside it: a line shorter in both editions, which the
   moment step spends (§8.1 №1). */
.run__refine-label {
  position: relative;
  float: left;
  margin: var(--space-4) var(--space-16) 0 0;
  padding: 0 var(--space-8);
  border: 1px solid var(--refine);
  border-radius: var(--radius-xs);
  font-size: var(--size-micro);
  line-height: 1.2;
  color: var(--refine);
  white-space: nowrap;
}
/* The screen's two notes are prose, and keep the page's widest reading
   measure: below 1080px the screen spans the container, and measured on
   2026-09-14 the V7 note ran 82 characters a line at 1024px and the first
   line note 87 at 768px and 1024px. */
.run__note {
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  color: var(--screen-ink-2);
  max-width: var(--measure-wide);
}

/* The beats of §5.6 №1 draw a light over a line and never take anything away:
   a hairline trace under a row, and a ring off the REFINE key. At rest both are
   invisible and nothing depends on them. site.js marks a beat with data-beat;
   the animations themselves are in the guard at the end of this section. */
.run__row::after, .run__fold::after, .run__pass0::after, .run__refine::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  border-top: 1px solid var(--screen-ink);
  transform-origin: 0 50%;
  opacity: 0;
  pointer-events: none;
}
.run__refine-label::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--refine);
  border-radius: var(--radius-xs);
  opacity: 0;
  pointer-events: none;
}

/* --- The draft/returned toggle ---------------------------------------------
 * Two radio inputs and nothing else. The radios sit in the DOM ahead of
 * everything they drive, which is what lets `:checked ~` reach the lines, the
 * labels and the outcome: with JavaScript blocked the toggle is whole.
 *
 * Both versions share one grid cell inside a clipped window; the returned one
 * starts a line above and descends over the draft, which leaves at the bottom.
 * Nothing fades and nothing is removed: the clip hides the version that is not
 * showing, and it stays in the accessibility tree.
 */
.swap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8) var(--space-16);
  min-width: 0;
  margin: 0;
  padding: var(--space-8) var(--space-16) var(--space-16);
  border: 0;
  border-top: 1px solid var(--rule);
}
/* The legend is floated so it is laid out as an ordinary item of the row: the
   question on the left, its two answers on the right, above the lines they
   switch. The keys follow the radios in focus order, so drawing them above the
   lines agrees with the order a keyboard meets them in. */
.swap__legend {
  float: left;
  flex: 1 1 auto;
  padding: 0;
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  font-size: var(--size-micro);
  line-height: var(--lh-body);
  color: var(--screen-ink-2);
}
/* The recorded lines, on one grid of two columns: what the gate changed, set
   at the moment step, and the note about it. Every level between the list and
   a version is a subgrid, so both lines and both versions share the one token
   column and every note starts on the same edge, whichever version shows. An
   engine without subgrid stacks each note under its token instead. */
.swap__stage {
  order: 1;
  flex: 1 1 100%;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--space-24);
  row-gap: var(--space-8);
}
.swap__line, .swap__window, .swap__window > .swap__strip {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
}
/* What both versions share — the recorded call — is drawn once, above what
   changed, and never rolls. */
.swap__call {
  grid-column: 1 / -1;
  font-size: var(--size-h3);
  line-height: 1.2;
  color: var(--screen-ink-2);
  white-space: pre-wrap;
}
/* clip, not hidden: a version rolled out of view still holds the footnote
   marker, and a box that clips by overflow: hidden scrolls itself to a link
   that takes the focus — measured on 2026-09-14, 72px, a whole line, under the
   version on screen. A clipped box never scrolls; the rule below brings the
   focused version into view instead. */
.swap__window { overflow: clip; }
.swap__strip { display: grid; grid-template-columns: minmax(0, 1fr); }
.swap__side {
  grid-row: 1;
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: subgrid;
  align-items: center;
}
.swap__side > code {
  font-size: var(--size-moment);
  /* One line of Latin code in either edition: Recursive's own 1.2em content
     box, which the Georgian leading would only pad. */
  line-height: 1.2;
  color: var(--screen-ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.swap__side--returned { transform: translateY(-100%); pointer-events: none; }
.swap__radio[value='returned']:checked ~ .swap__stage .swap__side--draft { transform: translateY(100%); pointer-events: none; }
.swap__radio[value='returned']:checked ~ .swap__stage .swap__side--returned { transform: translateY(0); pointer-events: auto; }
/* A keyboard reader who reaches the footnote marker while the gate's version
   shows is on a link the roll has taken out of view. The version the focus is
   in rolls back for as long as the focus stays in it. */
.swap__radio[value='returned']:checked ~ .swap__stage .swap__strip:has(.swap__side--draft:focus-within) .swap__side--draft { transform: translateY(0); }
.swap__radio[value='returned']:checked ~ .swap__stage .swap__strip:has(.swap__side--draft:focus-within) .swap__side--returned { transform: translateY(-100%); }
/* A note is a person's sentence about the version beside it (§8.1 №2), set in
   Sans in the screen and carried by that version, so it leaves when its
   version does. */
.swap__note {
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  font-size: var(--size-small);
  line-height: var(--lh-small);
  color: var(--screen-ink);
  max-width: var(--measure-wide);
}

.swap__options { display: flex; flex-wrap: wrap; gap: var(--space-8); }
.swap__option {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: var(--space-8) var(--space-16);
  background: var(--screen);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  font-size: var(--size-small);
  font-weight: var(--weight-medium);
  line-height: var(--lh-h3);
  color: var(--screen-ink-2);
  cursor: pointer;
}
.swap__option:hover { background: var(--screen-2); color: var(--screen-ink); }
.swap__option:active { background: var(--screen-2); }
.swap__radio[value='draft']:checked ~ .swap__options .swap__option[for='swap-draft'],
.swap__radio[value='returned']:checked ~ .swap__options .swap__option[for='swap-returned'] {
  background: var(--screen-2);
  color: var(--screen-ink);
  border-color: var(--screen-ink);
}
/* The radio is visually hidden but focusable, so the ring is drawn on the
   label a reader can see, in the tone that reads on a screen. */
.swap__radio[value='draft']:focus-visible ~ .swap__options .swap__option[for='swap-draft'],
.swap__radio[value='returned']:focus-visible ~ .swap__options .swap__option[for='swap-returned'] {
  outline: 2px solid var(--focus-gap);
  outline-offset: 2px;
}

/* The outcome: the one filled signal. The defect's red while the draft shows,
   the pass's green once the gate's version does. */
.swap__outcome { order: 2; flex: 1 1 100%; overflow: hidden; border-radius: var(--radius-sm); max-width: none; }
.swap__outcome .swap__strip { height: 100%; }
.swap__result {
  grid-area: 1 / 1;
  display: flex;
  align-items: center;
  padding: var(--space-8) var(--space-16);
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-h3);
  line-height: var(--lh-h3);
  font-weight: var(--weight-bold);
}
.swap__result--draft { background: var(--defect); color: var(--screen); }
.swap__result--returned { background: var(--pass); color: var(--screen); transform: translateY(-100%); }
.swap__radio[value='returned']:checked ~ .swap__outcome .swap__result--draft { transform: translateY(100%); }
.swap__radio[value='returned']:checked ~ .swap__outcome .swap__result--returned { transform: translateY(0); }

@media (prefers-reduced-motion: no-preference) {
  /* §5.6 №2, the version's roll, and №5, the control's colour. */
  .swap__side, .swap__result { transition: transform var(--duration-toggle) var(--ease-decelerate); }
  .swap__option { transition: background-color var(--duration-hover) var(--ease-standard), color var(--duration-hover) var(--ease-standard), border-color var(--duration-hover) var(--ease-standard); }
}

/* §5.6 №1, the recorded run played once. site.js sets data-beat on each part
   as its moment comes; only these two drawings answer it. */
@media (prefers-reduced-motion: no-preference) {
  .run__row[data-beat]::after, .run__fold[data-beat]::after, .run__pass0[data-beat]::after,
  .run__refine[data-beat]::after { animation: beat-trace var(--duration-beat) var(--ease-decelerate); }
  .run__refine-label[data-beat]::after { animation: beat-ring var(--duration-beat) var(--ease-decelerate); }
  @keyframes beat-trace {
    from { opacity: 1; transform: scaleX(0); }
    70% { opacity: 1; transform: scaleX(1); }
    to { opacity: 0; transform: scaleX(1); }
  }
  @keyframes beat-ring {
    from { opacity: 1; transform: scale(1); }
    to { opacity: 0; transform: scale(1.3, 1.6); }
  }
}

/* The compact screen (§5.4 S1, 390px): the six rows fold into their summary,
   which site.js closes on a narrow screen and the markup ships open. On a wide
   screen there is room for all six, so the summary is not drawn and the rows
   are always shown. */
@media (max-width: 719px) {
  .swap__option { padding-inline: var(--space-8); }
  .hero__inner { row-gap: var(--space-16); }
  /* A phone's screen has no room for a note beside the moment: it goes under
     the token it is about, and the call steps down with the running text. */
  .swap__stage { grid-template-columns: minmax(0, 1fr); }
  .swap__call { font-size: var(--size-body); }
}
@media (min-width: 720px) {
  .run > * { padding-inline: var(--space-24); }
  .run__summary { display: none; }
  .swap { padding: var(--space-16) var(--space-24) var(--space-24); }
}

/* --- S2. #verdict ----------------------------------------------------------------- */
/* Three tiers, left to right in the order a run takes them, each lens under the
   tier that runs it, all six at once. At the wide layout the pass bar sits
   under the third tier — the fold returns the verdict against it — on a
   subgrid, so its top lines up with the lenses under the first two. */
.tiers { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-32); }
.tier { position: relative; display: grid; align-content: start; gap: var(--space-16); min-width: 0; padding-top: var(--space-16); border-top: 1px solid var(--ink); }
/* The tier rule ends in an arrowhead, pointing on to the next tier. */
.tier:not(:last-child)::after {
  content: "";
  position: absolute;
  top: -5px;
  right: 0;
  width: 9px;
  height: 9px;
  background: var(--ink);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.tier__body { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }
.tier__names { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink); }
.tier__lenses { list-style: none; margin: 0; padding: 0; display: grid; }
.lens {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--space-8);
  row-gap: var(--space-4);
  padding-block: var(--space-8) var(--space-16);
  border-top: 1px solid var(--rule);
}
.lens__name {
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-small);
  line-height: var(--lh-small);
  font-weight: var(--weight-bold);
}
.lens__kind { font-size: var(--size-micro); color: var(--ink-2); }
.lens__what { flex-basis: 100%; font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }
.lens__floor { flex-basis: 100%; font-size: var(--size-micro); color: var(--ink-2); }

.bar { display: grid; align-content: start; gap: var(--space-16); margin-top: var(--space-48); padding-top: var(--space-24); border-top: 1px solid var(--ink); }
.bar__severities { display: grid; gap: var(--space-8); }
.bar__group { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-8); }
.bar__label { font-size: var(--size-small); line-height: var(--lh-small); font-weight: var(--weight-bold); color: var(--ink-2); }
.bar__body, .bar__rule { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }
.bar__rule { color: var(--ink); }
.sev {
  display: inline-flex;
  align-items: center;
  padding: 0 var(--space-8);
  border-radius: var(--radius-xs);
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-micro);
  line-height: var(--lh-body);
}
/* The pass bar's two kinds of severity (§8.4 №16). Blocking — CRITICAL and
   HIGH — is the filled key, solid ink; reported and not blocking — MEDIUM and
   LOW — is the outlined one, a rule-tone edge on the recessed metal. The
   difference is the meaning, not a style: the filled keys are the ones that
   stop a run. On the chassis a severity is told by fill and word, never by a
   signal colour. */
.sev--blocking { background: var(--ink); border: 1px solid var(--ink); color: var(--chassis); }
.sev--reported { background: var(--chassis-2); border: 1px solid var(--rule); color: var(--ink-2); }

@media (min-width: 720px) {
  .tiers { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--grid-gutter); }
}
@media (min-width: 1080px) {
  .bar { margin-top: var(--space-48); }
  @supports (grid-template-rows: subgrid) {
    .verdict .container {
      display: grid;
      grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
      column-gap: var(--grid-gutter);
    }
    .verdict .section-head { grid-column: 1 / -1; }
    .tiers { grid-column: 1 / -1; grid-row: 2 / span 2; grid-template-columns: subgrid; grid-template-rows: subgrid; row-gap: 0; }
    .tier:nth-child(1) { grid-column: 1 / span 4; grid-row: 1 / span 2; }
    .tier:nth-child(2) { grid-column: 5 / span 4; grid-row: 1 / span 2; }
    .tier:nth-child(3) { grid-column: 9 / span 4; grid-row: 1; }
    .bar { grid-column: 9 / span 4; grid-row: 3; margin-top: var(--space-32); }
  }
}

/* --- S3. #gates ------------------------------------------------------------------ */
/* The nine stages, drawn once, as an instrument: ONE ink stroke, a node per
   stage, numbered 01-09 in the order a run takes them. Each stage draws the
   stretch of the stroke from its node to the next; the stretches meet, so the
   stroke is unbroken except where a human gate cuts it. A gate is a break, the
   mark's own gap, wider than the space between two stages, with the gate's
   marker standing on the stroke inside it: two posts, the run held between
   them (§5.4 S3, §8.2 №5). A conditional stage is unarmed: a hollow node on a
   dashed stretch. REFINE's return to VERIFIED is a dashed loop over the
   stroke, labelled with its cap. The stroke never wraps: below 1080px the axis
   turns vertical and stays one line.

   A gate breaks the line before the stage it holds — at CLARIFY and at OUTPUT
   the run stops on arriving, and after GROUNDED it stops before CODED — so the
   three breaks come before CLARIFY, CODED and OUTPUT.

   The geometry is named once, on .gates, and every offset is derived from it:
   a node centres on the stroke, a gate's posts centre in its break, and the
   loop leaves one node and lands on the one before it. Change one value and
   the stroke, the nodes and the breaks move together. */
.gates {
  --stroke: 2px;
  --node: 12px;
  --post: 20px;
  --stroke-x: 25px;
  --stroke-y: 22px;
  --gate-break: var(--space-48);
  --stage-gap: var(--space-4);
}
.stages {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
}
.stage { position: relative; min-width: 0; padding-left: var(--space-48); }
.stage::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--stroke-x);
  border-inline-start: var(--stroke) solid var(--ink);
}
.stage:first-child::before { top: var(--stroke-y); }
.stage:last-child::before { bottom: auto; height: var(--stroke-y); }
.stage--conditional::before { border-inline-start-style: dashed; }
.stage::after {
  content: "";
  position: absolute;
  z-index: 2;
  top: calc(var(--stroke-y) - var(--node) / 2);
  left: calc(var(--stroke-x) + (var(--stroke) - var(--node)) / 2);
  width: var(--node);
  height: var(--node);
  background: var(--ink);
}
/* Hollow: the chassis inside an ink edge as heavy as the stroke. */
.stage--conditional::after { background: var(--chassis); border: var(--stroke) solid var(--ink); }
.stage__details { position: static; }
.stage__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  gap: var(--space-4) var(--space-8);
  min-height: 44px;
  cursor: pointer;
  list-style: none;
}
.stage__summary::-webkit-details-marker { display: none; }
/* The disclosure's own affordance, drawn: a small ink triangle that turns down
   when the stage is open. It turns at once; §5.6 moves the height, not this. */
.stage__summary::before {
  content: "";
  flex: none;
  width: 7px;
  height: 8px;
  background: var(--ink);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.stage__details[open] > .stage__summary::before { transform: rotate(90deg); }
.stage__summary:hover .stage__id { text-decoration: underline; text-decoration-color: var(--rule); text-underline-offset: 0.25em; }
.stage__summary:active { background: var(--chassis-2); }
/* The stage's place in the sequence (§8.2 №7): two tabular figures in the
   secondary ink, ahead of the id they number. */
.stage__n {
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-micro);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}
/* The id reads at the small step (§8.2 №6: at least 14px); it was 12px. */
.stage__id {
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-small);
  line-height: var(--lh-h3);
  font-weight: var(--weight-bold);
  color: var(--ink);
}
.stage__what { padding-bottom: var(--space-16); font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }

/* A break: the stage a gate holds starts one break further along the axis,
   and the gate's marker stands in the space left. On the vertical axis that
   is space above the stage. */
.stage:has(> .stage__gate--at), .stage:has(> .stage__gate--after) + .stage { margin-top: var(--gate-break); }
.stage__gate {
  position: absolute;
  left: calc(var(--stroke-x) + (var(--stroke) - var(--post)) / 2);
  width: var(--post);
  height: var(--gate-break);
}
.stage__gate--at { bottom: 100%; }
.stage__gate--after { top: 100%; }
/* The two posts, across the stroke's axis. */
.stage__gate::before {
  content: "";
  position: absolute;
  inset: calc(50% - 4px) 0 auto;
  height: 8px;
  border-block: var(--stroke) solid var(--ink);
}

/* The REFINE loop: back to VERIFIED, dashed like everything conditional, from
   this stage's node up the side of the stroke to the node before it, ending
   in an arrowhead there. Its label is the cap, from shared.json. */
.stage__loop {
  position: absolute;
  top: calc(-1 * var(--stroke-y));
  left: 0;
  width: var(--stroke-x);
  height: calc(2 * var(--stroke-y));
  display: flex;
  align-items: center;
  justify-content: center;
  border: var(--stroke) dashed var(--ink);
  border-inline-end: 0;
  border-radius: var(--radius-md) 0 0 var(--radius-md);
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-micro);
  line-height: 1;
  color: var(--ink);
  white-space: nowrap;
}
/* The cap's figure, on the chassis it is drawn over, so the dashes stop short
   of it on both sides. */
.stage__cap { padding-inline: var(--space-4); background: var(--chassis); }
.stage__loop::after {
  content: "";
  position: absolute;
  top: -5px;
  right: 0;
  width: 6px;
  height: 8px;
  background: var(--ink);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}

.gates__block { display: grid; gap: var(--space-24); margin-top: var(--space-32); }
.gates__intro { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink); max-width: var(--measure-wide); }
.gates__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-24); }
.gates__item { display: grid; align-content: start; gap: var(--space-8); }
/* Below 1080px a gate's column is not under its break, so each carries the
   marker it stands for: the same two posts, drawn across a short stroke. */
.gates__item::before {
  content: "";
  width: 8px;
  height: var(--post);
  margin-bottom: var(--space-8);
  border-inline: var(--stroke) solid var(--ink);
}
.gates__body { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }
.gates__aside, .gates__evidence { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); max-width: var(--measure-wide); }
.gates__evidence { color: var(--ink); }
.gates__lines { display: grid; gap: var(--space-16); margin-top: var(--space-24); padding-top: var(--space-24); border-top: 1px solid var(--rule); }
.gates__core, .gates__lines .compact__body { display: grid; align-content: start; gap: var(--space-16); }
.gates__line { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); max-width: var(--measure-wide); }
.gates__line strong { color: var(--ink); font-weight: var(--weight-bold); }

@media (min-width: 720px) {
  .gates__list { grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--grid-gutter); }
}

/* --- The instrument, horizontal ------------------------------------------------ */
/* From 1080px the stroke runs left to right over the section's own tracks: one
   per stage, and one break before each stage a gate holds. The tracks are
   shared.json's human_gates, and tests/test_build.py derives them from it and
   fails when the two part: two stages, a break, three, a break, three, a
   break, and OUTPUT. OUTPUT's track is as wide as the three before it, so
   each gate's column below — its break and the stages it holds — is the same
   width, and the columns start exactly where the breaks do (§8.2 №5). The
   first two stages hold no gate; their column holds the sentence the gates
   hang on. */
@media (min-width: 1080px) {
  /* Below 1280px a stage's track is 81px at the narrowest, and CAPTURED is 72:
     the break takes a smaller step and the labels no gap, so every id and tag
     keeps inside its own track. */
  .gates { --stroke-y: 28px; --loop-rise: 20px; --gate-break: var(--space-32); --stage-gap: 0px; }
  .gates .container {
    display: grid;
    grid-template-columns:
      repeat(2, minmax(0, 1fr)) var(--gate-break)
      repeat(3, minmax(0, 1fr)) var(--gate-break)
      repeat(3, minmax(0, 1fr)) var(--gate-break)
      minmax(0, 3fr);
  }
  .gates .section-head, .gates__lines { grid-column: 1 / -1; }
  .stages, .gates__block {
    grid-column: 1 / -1;
    grid-template-columns: subgrid;
    align-items: start;
  }
  .stage:nth-child(3) { grid-column-start: 4; }
  .stage:nth-child(6) { grid-column-start: 8; }
  .stage:nth-child(9) { grid-column-start: 12; }

  .stage { padding: var(--space-48) var(--stage-gap) 0 0; }
  .stage:has(> .stage__gate--at), .stage:has(> .stage__gate--after) + .stage { margin-top: 0; }
  .stage::before {
    top: calc(var(--stroke-y) - var(--stroke) / 2);
    bottom: auto;
    left: calc(var(--node) / 2);
    right: 0;
    border-inline-start: 0;
    border-top: var(--stroke) solid var(--ink);
  }
  .stage:last-child::before { right: auto; width: 0; height: auto; }
  /* A conditional stage is dashed on both sides: the stretch into it, which
     the stage before draws, and the stretch out of it. */
  .stage--conditional::before, .stage:has(+ .stage--conditional)::before { border-top-style: dashed; }
  .stage::after { left: 0; }
  .stage__gate {
    top: calc(var(--stroke-y) - var(--post) / 2);
    bottom: auto;
    width: var(--gate-break);
    height: var(--post);
  }
  .stage__gate--at { left: auto; right: 100%; }
  .stage__gate--after { left: 100%; }
  .stage__gate::before { inset: 0 auto 0 calc(50% - 4px); width: 8px; height: auto; border-block: 0; border-inline: var(--stroke) solid var(--ink); }
  .stage__loop {
    top: calc(var(--stroke-y) - var(--loop-rise));
    left: calc(var(--node) / 2 - 100%);
    width: 100%;
    height: var(--loop-rise);
    align-items: flex-start;
    border: var(--stroke) dashed var(--ink);
    border-bottom: 0;
    border-radius: var(--radius-md) var(--radius-md) 0 0;
  }
  /* The label sits on the loop, knocked out of it by the chassis it is on. */
  .stage__cap { transform: translateY(calc(-50% - var(--stroke) / 2)); }
  .stage__loop::after { top: auto; bottom: -6px; right: auto; left: -5px; width: 8px; height: 6px; clip-path: polygon(0 0, 100% 0, 50% 100%); }
  /* The number and the triangle on the first line, the id always on the next:
     a column is too narrow to hold both for some ids and not others, and nine
     ragged starts read as nine different layouts. */
  .stage__summary { align-content: flex-start; }
  .stage__id { flex-basis: 100%; }
  /* A tag wraps inside its own column — at its spaces first, and inside a word
     only where the word alone is wider than the column, the same rule as
     .stage__what. */
  .stage .tag { white-space: normal; overflow-wrap: anywhere; hyphens: auto; }
  /* A column is narrower than the longest words: with every stage open, they
     break inside the column instead of running under the next stage. */
  .stage__what { width: 0; min-width: 100%; padding-top: var(--space-8); overflow-wrap: anywhere; hyphens: auto; }

  .gates__block { gap: var(--space-24) 0; }
  .gates__intro { grid-column: 1 / 3; padding-inline-end: var(--space-24); }
  .gates__list { grid-column: 3 / -1; grid-template-columns: subgrid; column-gap: 0; }
  /* The column starts at its break; its words start under the marker's first
     post, which stands at the middle of the break. */
  .gates__item { padding-inline: calc(var(--gate-break) / 2 - var(--space-4)) var(--space-24); }
  .gates__item::before { display: none; }
  .gates__item:nth-child(1) { grid-column: 1 / 5; }
  .gates__item:nth-child(2) { grid-column: 5 / 9; }
  .gates__item:nth-child(3) { grid-column: 9 / 11; }
  .gates__evidence { grid-column: 3 / 7; padding-inline: calc(var(--gate-break) / 2 - var(--space-4)) var(--space-24); }
  .gates__aside { grid-column: 7 / -1; padding-inline-start: calc(var(--gate-break) / 2 - var(--space-4)); }
  /* What keeps a run whole on the left, how larger changes are woven and what
     keeps a run safe on the right, on the grid's one asymmetry. */
  .gates__lines { grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr)); column-gap: var(--grid-gutter); }
  .gates__core { grid-column: 1 / span 6; }
  .gates__lines > .compact { grid-column: 7 / span 6; }
}
@media (min-width: 1280px) {
  .gates { --gate-break: var(--space-64); --stage-gap: var(--space-8); }
}

/* --- S4. #skills ---------------------------------------------------------------- */
/* The category cards in a scroll rail, compact (§5.4 S4): the only cards on
   the chassis, and flat on it: a rule-tone edge and no cast, because nothing
   read on the metal lifts off it (§8.3 №11). The rail is a named, focusable
   group a keyboard can scroll; it contains its own sideways overscroll and
   leaves vertical scroll to the page. */
.skills__rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(168px, 1fr);
  gap: var(--space-16);
  padding-block: var(--space-4) var(--space-16);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.skills__rail > * { scroll-snap-align: start; }
.skills__rail:focus-visible { outline-offset: 4px; }
.skills__cat {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  padding: var(--space-16);
  background: var(--chassis);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
}
.skills__catline { display: grid; gap: 0; font-size: var(--size-small); line-height: var(--lh-h3); }
.skills__count { font-size: var(--size-micro); font-weight: var(--weight-regular); color: var(--ink-2); }
.skills__examples { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-4); }
.skills__example {
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-micro);
  line-height: var(--lh-h3);
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
.skills__selector, .skills__caveat { margin-top: var(--space-24); font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); max-width: var(--measure-wide); }
.skills__selector { color: var(--ink); }
.skills__citation { margin-top: var(--space-8); font-size: var(--size-small); line-height: var(--lh-small); }

@media (min-width: 1080px) {
  .skills .container { display: grid; grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr)); column-gap: var(--grid-gutter); }
  .skills__rail { grid-column: 1 / -1; }
  /* No lead here, and the heading is longer than the lead's measure, so the
     head takes eight columns: the same one-column head, sized to what it holds. */
  .skills .section-head { grid-column: 1 / span 8; max-width: none; }
  .skills__rail { gap: var(--grid-gutter); }
  .skills__selector { grid-column: 1 / span 6; }
  .skills__caveat { grid-column: 7 / span 6; }
  .skills__citation { grid-column: 7 / span 6; margin-top: var(--space-8); }
}

/* --- S5. #proof ------------------------------------------------------------------ */
.proof__audit { display: grid; gap: var(--space-24); min-width: 0; }
.proof__audit > h3 { font-size: var(--size-h3); }
/* The one poster-size element on the page (§5.4 S5). */
.proof__poster { display: grid; gap: var(--space-8); max-width: none; }
.proof__figure {
  font-size: var(--size-display);
  line-height: var(--lh-display);
  letter-spacing: var(--track-display);
  font-weight: var(--weight-display);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* Georgian from 320 to 339px: "4‑დან 3" at the poster minimum is 307px wide
   against a 288-307px column, and wrapped as "4‑დან" over "3". The scale
   cannot give the room back — 75px is the ninth step above a 12px floor at
   1.25 each, the least a poster can be — so the figure gives it from its own
   setting: it hangs one scale step into each page margin, as display type
   sets its side bearings outside the measure, and closes its one word space
   by a tenth of an em. 299.5px against 304px at 320, measured in Chromium on
   2026-09-14; English, 210px, never needs it. */
@media (max-width: 339px) {
  :lang(ka) .proof__figure { margin-inline: calc(-1 * var(--space-8)); word-spacing: -0.1em; }
}
.proof__figure-line { font-size: var(--size-h3); line-height: var(--lh-h3); color: var(--ink); }
.proof__explain { color: var(--ink-2); max-width: var(--measure-lead); }

/* The ledger: the machine's record, in a screen. */
/* Inset by one step on every side, so neither the caption nor the rows (or,
   on a phone, the stacked blocks) run flush into the bezel. */
.ledger {
  min-width: 0;
  padding: var(--space-8);
  background: var(--screen);
  color: var(--screen-ink);
  border: 1px solid var(--rule);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-bezel);
  overflow-x: auto;
}
.ledger__table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-small);
  line-height: var(--lh-h3);
  font-variant-numeric: tabular-nums;
}
.ledger__caption {
  padding: var(--space-16) var(--space-24);
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  color: var(--screen-ink-2);
}
.ledger__source { color: var(--screen-ink); text-decoration-color: var(--screen-ink-2); }
.ledger__table th, .ledger__table td { padding: var(--space-4) var(--space-16); border-top: 1px solid var(--rule); text-align: left; vertical-align: top; }
.ledger__colhead {
  padding-block: var(--space-8);
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  font-size: var(--size-micro);
  font-weight: var(--weight-medium);
  color: var(--screen-ink-2);
  vertical-align: bottom;
}
.ledger__colhead:nth-child(4), .ledger__colhead:nth-child(n+6) { text-align: right; }
/* A lens column is headed by its code (§8.4 №15), in the machine's voice like
   every lens id, whole on one line, and dotted where its full id is one hover
   or one screen-reader announcement away. */
.ledger__code {
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  white-space: nowrap;
  text-decoration: underline dotted;
  text-decoration-color: var(--screen-ink-2);
  text-underline-offset: 0.25em;
  cursor: help;
}
.ledger__table tbody th { font-weight: var(--weight-medium); color: var(--screen-ink); white-space: nowrap; }
.ledger__table .num { text-align: right; }
.ledger__table td:first-of-type { white-space: nowrap; }
.ledger__task {
  font-family: var(--font-sans);
  font-variation-settings: var(--axis-sans);
  color: var(--screen-ink);
  width: 24%;
}
.ledger__result { white-space: nowrap; }
.ledger__result .icon { display: inline-block; vertical-align: -2px; margin-right: var(--space-4); }
.ledger__result--unverified { color: var(--defect); }
.ledger__result--ok { color: var(--pass); }
/* The cell labels only a stacked row needs; the column headers carry them on
   a table that has its columns. */
.ledger__label { display: none; }

.fn-ref {
  position: relative;
  top: -0.4em;
  font-size: var(--size-micro);
  line-height: 0;
  font-weight: var(--weight-bold);
  text-decoration: none;
  padding-inline: var(--space-4);
}
.fn-ref:hover { text-decoration: underline; }
.fn-back { font-size: var(--size-micro); color: var(--ink-2); white-space: nowrap; }
.proof__footnotes { display: grid; gap: var(--space-8); margin: 0; padding-left: var(--space-24); font-size: var(--size-micro); line-height: var(--lh-small); color: var(--ink-2); }
.proof__footnotes li { max-width: var(--measure-wide); }
.proof__footnotes li::marker { font-variant-numeric: tabular-nums; color: var(--ink); }
.proof__findings-title { margin-top: var(--space-24); font-size: var(--size-body); }
.proof__findings { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-24); }
/* The margin notes: one finding per lens, told by its lens and its record,
   ruled like a note in a margin, not boxed like a card. */
.finding { display: grid; align-content: start; gap: var(--space-8); padding-top: var(--space-16); border-top: 1px solid var(--ink); }
.finding__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-8);
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-micro);
  color: var(--ink-2);
}
.finding__lens { color: var(--ink); font-weight: var(--weight-bold); }
.finding__text { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }
.proof__skills { display: grid; gap: var(--space-16); margin-top: var(--space-32); }
.proof__skills p { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); max-width: var(--measure-wide); }
.proof__note {
  margin-top: var(--space-24);
  font-size: var(--size-small);
  line-height: var(--lh-small);
  color: var(--ink-2);
}

@media (min-width: 720px) {
  .proof__findings { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--grid-gutter); }
}
@media (min-width: 1080px) {
  .proof__audit { grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr)); column-gap: var(--grid-gutter); row-gap: var(--space-24); }
  .proof__audit > h3, .ledger, .proof__footnotes, .proof__findings-title, .proof__audit > .compact { grid-column: 1 / -1; }
  .proof__poster { grid-column: 1 / span 5; }
  .proof__explain { grid-column: 7 / span 6; align-self: end; }
  .proof__footnotes { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--space-48); }
  .proof__findings-title { margin-top: 0; }
  /* Three columns of the grid, filled in lens order and balanced, so a long
     note does not leave a row of short ones standing in its white. */
  .proof__findings { display: block; column-count: 3; column-gap: var(--grid-gutter); }
  .finding { break-inside: avoid; margin-bottom: var(--space-16); }
  .proof__skills { grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr)); column-gap: var(--grid-gutter); }
  .proof__skills h3 { grid-column: 1 / span 5; }
  .proof__skills p { grid-column: 7 / span 6; }
  .proof__note { margin-inline-start: calc(50% + var(--grid-gutter) / 2); }
}

/* The ledger stacked (§5.4 S5, 390px): each run a block of its own, header
   first, the six lens counts in a mini-row. The table's semantics are kept by
   the roles the markup states on every part, which a changed display would
   otherwise take away in some engines. */
@media (max-width: 719px) {
  .ledger__table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .ledger__table, .ledger__table tbody { display: block; }
  .ledger__caption { display: block; }
  .ledger__row {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: var(--space-4) var(--space-16);
    padding: var(--space-16);
    border-top: 1px solid var(--rule);
  }
  .ledger__table .ledger__row > th, .ledger__table .ledger__row > td { display: block; padding: 0; border-top: 0; text-align: left; }
  /* The run and its result; the task; the date, the refine passes and the
     total on one line; then the six lens counts, two to a line, each beside
     its name. */
  .ledger__row > th { grid-row: 1; grid-column: 1 / span 3; }
  .ledger__row > td:nth-of-type(4) { grid-row: 1; grid-column: 4 / span 3; text-align: right; }
  .ledger__row > td:nth-of-type(2) { grid-row: 2; grid-column: 1 / -1; }
  .ledger__row > td:nth-of-type(1) { grid-row: 3; grid-column: 1 / span 2; }
  .ledger__row > td:nth-of-type(3) { grid-row: 3; grid-column: 3 / span 2; }
  .ledger__row > td:last-child { grid-row: 3; grid-column: 5 / span 2; }
  .ledger__row > td:nth-of-type(n+5):not(:last-child) { grid-column: span 3; }
  .ledger__table .ledger__row > .num { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-8); }
  .ledger__label {
    display: block;
    font-family: var(--font-sans);
    font-variation-settings: var(--axis-sans);
    font-size: var(--size-micro);
    color: var(--screen-ink-2);
    overflow-wrap: anywhere;
  }
  .ledger__task { width: auto; }
}

/* --- S6. #limits: the plainest block on the page ----------------------------------- */
/* One column at a reading measure: a title and a sentence per limit, and a rule
   between them. No surface, no icon, no numbering, no severity (§5.4 S6). */
.limits__list { list-style: none; margin: 0; padding: 0; max-width: var(--measure-wide); }
.limits__item { padding-block: var(--space-16); border-top: 1px solid var(--rule); }
.limits__item:first-child { border-top: 1px solid var(--ink); }
.limits__item h3 { font-size: var(--size-body); line-height: var(--lh-h3); }
.limits__body { margin-top: var(--space-4); color: var(--ink-2); }

@media (min-width: 1080px) {
  .limits .container { display: grid; grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr)); column-gap: var(--grid-gutter); }
  /* The plainest block keeps its head beside the list rather than above it:
     the same one-column head, in the first four columns, and the five limits
     in the other eight. */
  .limits .section-head { grid-column: 1 / span 4; }
  .limits .section-head .lead { font-size: var(--size-small); line-height: var(--lh-small); }
  .limits__list { grid-column: 5 / span 8; }
}

/* --- S7. #install ------------------------------------------------------------------ */
/* minmax(0, 1fr) at every width. With no declared track a grid column is
   `auto` and sizes to its widest unbreakable content, so on a 390px phone the
   install blocks measured 575-583px and the whole page scrolled 217px
   sideways in both languages. */
.install__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-32); }
.install__req { display: grid; align-content: start; gap: var(--space-16); }
.install__req p { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); }
.install__steps { display: grid; gap: var(--space-16); margin: 0; padding-left: var(--space-24); }
.install__steps > li::marker { font-weight: var(--weight-bold); color: var(--ink); font-variant-numeric: tabular-nums; }
.install__step { display: list-item; }
.install__step > * + * { margin-top: var(--space-8); }
.install__step h3 { font-size: var(--size-body); line-height: var(--lh-h3); }
.install__label { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); max-width: var(--measure-wide); }
/* The three entry points are written inline in a sentence, each one unbroken
   33-character identifier: allowed to break anywhere, it cannot push a 320px
   phone sideways. */
.install__label code { overflow-wrap: anywhere; }
.install__contract { display: grid; gap: var(--space-4); margin: 0; font-size: var(--size-small); line-height: var(--lh-small); }
.install__contract-field { display: grid; grid-template-columns: 6rem minmax(0, 1fr); gap: var(--space-16); }
/* The widest reading measure, like every other line of prose here: below
   1080px a step spans the container, and measured on 2026-09-14 the scope
   annotation set 81 characters on one line at 1024px, the Georgian one 85. */
.install__contract-field dd { color: var(--ink-2); max-width: var(--measure-wide); }
.install__extra { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-48); margin-top: var(--space-32); padding-top: var(--space-32); border-top: 1px solid var(--rule); }
.install__adds, .install__hosts, .install__codex, .install__guard, .install__opencode,
.install__headless, .install__dev { display: grid; align-content: start; gap: var(--space-16); min-width: 0; }
.install__extra h3 { font-size: var(--size-body); line-height: var(--lh-h3); }
.install__adds-list { display: grid; gap: var(--space-16); margin: 0; font-size: var(--size-small); line-height: var(--lh-small); }
.install__add dt { font-weight: var(--weight-bold); }
.install__add dd { color: var(--ink-2); }
.install__hooks { display: grid; gap: var(--space-4); margin: 0 0 var(--space-8); }
.install__hook { display: flex; flex-wrap: wrap; gap: 0 var(--space-8); }
.install__hook dt { font-weight: var(--weight-regular); color: var(--ink); }
/* The packaged Codex profile, grouped by its setting: the same two-part line
   as a hook's file and its events, so the block adds a fact and not a new
   component. The setting is the term — full ink, and the weight the other
   definition lists give a term — and the roles that take it are the
   definition; the space between two groups is larger than the space inside
   one, or six lines read as six headings. */
.install__roles { display: grid; gap: var(--space-16); margin: 0; font-size: var(--size-small); line-height: var(--lh-small); }
.install__role { display: grid; gap: 0 var(--space-8); }
.install__role dt { display: flex; flex-wrap: wrap; gap: 0 var(--space-8); font-weight: var(--weight-bold); color: var(--ink); }
.install__role dd { margin: 0; color: var(--ink-2); }
.install__ports { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--space-8); }
.install__ports a { white-space: nowrap; }
.install__cmds { display: grid; gap: var(--space-8); }
/* A flag written in prose is one word: a browser breaks a line after a hyphen,
   and `--hosts` broke as `-` / `-hosts` in the Georgian column, which reads as
   two tokens and copies as a flag that does not exist. Held on one line from
   720px, where every column is wide enough for the longest of them; below that
   the column is the full page and the words wrap normally. */
@media (min-width: 720px) {
  .install__extra p code { white-space: nowrap; }
}
.install__extra .compact__body { display: grid; align-content: start; gap: var(--space-16); }
.install__hosts p, .install__codex p, .install__guard p, .install__opencode p,
.install__headless p, .install__dev p { font-size: var(--size-small); line-height: var(--lh-small); color: var(--ink-2); max-width: var(--measure-wide); }
.install__guard .install__status { color: var(--ink); }
/* The headless flag removes every approval prompt. The sentence that says so is
   plain text on the chassis, like every other paragraph: full ink, and a
   strong lead phrase — the one bold run in the block — directly above the
   command it is about. No fill, no border, no glyph: the chassis carries no
   cards but the skills, and no signal colour at all (§5.11 №1). */
.install__headless .install__warning { color: var(--ink); }
.install__warning strong { font-weight: var(--weight-bold); }

@media (min-width: 720px) {
  .install__extra { display: block; columns: 2; column-gap: var(--grid-gutter); }
  .install__extra > * { break-inside: avoid; margin-bottom: var(--space-32); }
  .install__extra > :last-child { margin-bottom: 0; }
  /* The three entry points in the sentence stay whole wherever a column is
     wide enough to hold one, which is every column from 720px. */
  .install__label code { white-space: nowrap; }
}
/* Three columns of four from 1080px (§8.4 №14): the requirements with the
   sentence naming the three commands, the three install steps, and the first
   real run with its contract. Two columns left the requirements and the
   sentence 250-270px short of the four steps beside them, an empty band under
   the sentence, at every width they were used; v2.1 first balanced from 1280,
   which left the band from 1080 to 1279. Measured 2026-09-14, the three
   columns end within 74px of each other in English and 60px in Georgian at
   1080, 1180, 1280, 1440 and 1600, and within 47 and 24px from 1440. The steps are one ordered
   list across the last two columns, on a subgrid, so the numbering runs on. */
@media (min-width: 1080px) {
  .install__grid { grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr)); grid-template-rows: auto 1fr; column-gap: var(--grid-gutter); row-gap: var(--space-32); }
  .install__req { grid-column: 1 / span 4; grid-row: 1; }
  .install__grid > .install__label { grid-column: 1 / span 4; grid-row: 2; }
  .install__steps {
    grid-column: 5 / span 8;
    grid-row: 1 / span 2;
    display: grid;
    grid-template-columns: subgrid;
    align-content: start;
    padding-left: 0;
  }
  /* Each step keeps a step of margin for its number, so the numbers hang in
     the gutter plus that margin and never over the column before them. */
  .install__step { grid-column: 1 / span 4; margin-inline-start: var(--space-24); }
  .install__step:last-child { grid-column: 5 / span 4; grid-row: 1 / span 3; }
  .install__extra { columns: 3; }
}

/* --- S8. Footer ------------------------------------------------------------------- */
/* The page ends anchored: a band of ink rule, the brand, the same four places
   the header names, and the colophon. */
.site-footer {
  background: var(--chassis);
  border-top: 2px solid var(--ink);
  padding-block: var(--space-32) var(--space-48);
}
.footer__top { display: grid; gap: var(--space-32); }
.footer__brand { max-width: var(--measure-lead); }
.footer__tagline { margin-top: var(--space-8); color: var(--ink-2); font-size: var(--size-small); line-height: var(--lh-small); }
.footer__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-24); }
.footer__coltitle { font-size: var(--size-small); line-height: var(--lh-small); font-weight: var(--weight-bold); color: var(--ink); }
.footer__list { list-style: none; margin: var(--space-8) 0 0; padding: 0; display: grid; }
.footer__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  min-height: 44px;
  color: var(--ink-2);
  font-size: var(--size-small);
  line-height: var(--lh-small);
  text-decoration: none;
}
.footer__link:hover { color: var(--ink); text-decoration: underline; }
.footer__plain {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink-2);
  font-size: var(--size-small);
  line-height: var(--lh-small);
}
.footer__bottom {
  margin-top: var(--space-32);
  padding-top: var(--space-16);
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-16) var(--space-32);
  align-items: center;
}
.footer__version {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  font-family: var(--font-mono);
  font-variation-settings: var(--axis-mono);
  font-size: var(--size-micro);
  color: var(--ink-2);
}
.footer__notes { display: grid; gap: var(--space-4); flex: 1 1 20rem; }
.footer__fine {
  color: var(--ink-3);
  font-size: var(--size-micro);
  max-width: var(--measure);
}
.footer__colophon { color: var(--ink-2); }

@media (min-width: 1080px) {
  .footer__top { grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr)); column-gap: var(--grid-gutter); }
  .footer__brand { grid-column: 1 / span 5; }
  .footer__cols { grid-column: 7 / span 6; }
}

/* --- A phone: the sections' own spacing ------------------------------------ */
/* Last in the sheet, so each step down overrides the rule it tightens. */
@media (max-width: 719px) {
  /* Measured on 2026-09-14 at 390px: these steps down, one place each on the
     4-96 scale, took 352px off the Georgian page (14,810 to 14,458) without
     touching a line of text. Separation between sections and blocks stays
     larger than the space inside them. */
  .section { padding-block: var(--space-24); }
  .tiers { gap: var(--space-24); }
  .tier, .bar { gap: var(--space-8); }
  .gates__block { margin-top: var(--space-24); }
  .gates__list { gap: var(--space-16); }
  .install__extra { margin-top: var(--space-24); padding-top: var(--space-24); gap: var(--space-24); }
  .install__adds, .install__hosts, .install__codex, .install__guard, .install__opencode,
  .install__headless, .install__dev,
  .install__extra .compact__body, .install__req { gap: var(--space-8); }
  .install__grid { gap: var(--space-24); }
  .install__steps { gap: var(--space-8); }
  .gates__block, .proof__audit { gap: var(--space-16); }
  .gates__lines, .gates__core, .gates__lines .compact__body { gap: var(--space-8); }
  .bar { margin-top: var(--space-24); }
  .skills__selector, .skills__caveat { margin-top: var(--space-16); }
  .proof__skills { margin-top: var(--space-24); gap: var(--space-8); }
  .lens { padding-block: var(--space-8); }
  .finding { padding-top: var(--space-8); }
  .proof__findings { gap: var(--space-16); }
  .limits__item { padding-block: var(--space-8); }
  .footer__top { gap: var(--space-24); }
  .footer__cols { gap: var(--space-16); }
}

/* --- The phone's compact form (sections.compact) --------------------------- */
/* Secondary detail folds behind a summary below 720px, where the page has to
   fit its 15,000px phone budget in Georgian. The markup ships every one open
   and site.js closes them on a narrow screen; from 720px up the summary is not
   drawn and the script keeps them open, so a wide screen shows everything and
   no reader can close what they cannot see a control for. The summary is the
   stage disclosure's affordance at the running-text size: an ink triangle that
   turns down when open, and a 44px target. */
.compact__summary {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-height: 44px;
  font-size: var(--size-small);
  line-height: var(--lh-small);
  font-weight: var(--weight-medium);
  color: var(--ink);
  cursor: pointer;
  list-style: none;
}
.compact__summary::-webkit-details-marker { display: none; }
.compact__summary::before {
  content: "";
  flex: none;
  width: 7px;
  height: 8px;
  background: var(--ink);
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}
.compact[open] > .compact__summary::before { transform: rotate(90deg); }
.compact__summary:hover { text-decoration: underline; text-decoration-color: var(--rule); text-underline-offset: 0.25em; }
.compact__summary:active { background: var(--chassis-2); }
.compact[open] > .compact__summary { margin-bottom: var(--space-8); }
@media (min-width: 720px) {
  .compact__summary { display: none; }
}

/* --- Disclosure (§5.6 №3) -------------------------------------------------- */
/* A native <details> opens by growing to its content over 240ms and closes over
   160ms, both on the decelerating curve. Engines without ::details-content or
   without keyword interpolation open and close at once, which is also what a
   reader who asked for reduced motion gets everywhere. */
@media (prefers-reduced-motion: no-preference) {
  .stage__details, .run__lenses, .compact { interpolate-size: allow-keywords; }
  .stage__details::details-content, .run__lenses::details-content, .compact::details-content {
    block-size: 0;
    overflow-y: clip;
    transition: block-size var(--duration-close) var(--ease-decelerate),
      content-visibility var(--duration-close) var(--ease-decelerate) allow-discrete;
  }
  .stage__details[open]::details-content, .run__lenses[open]::details-content, .compact[open]::details-content {
    block-size: auto;
    transition-duration: var(--duration-open);
  }
}

/* --- 404 ----------------------------------------------------------------- */
.notfound { display: grid; align-items: center; min-height: 60vh; }
.notfound__inner { padding-block: var(--space-96); }
/* The mark at ten times the header's size: the display form, in ink. */
.notfound__mark { display: block; width: 300px; max-width: 100%; height: auto; color: var(--ink); margin-bottom: var(--space-48); }
.notfound p { margin-top: var(--space-24); color: var(--ink-2); }
.notfound .btn { margin-top: var(--space-48); }

/* --- Print ----------------------------------------------------------------- */
/* Nothing is armed for paper: no beat, no sticky bar over the first lines.
   Every fold prints open at its full height (§5.6: a printed page is never
   blank). site.js opens the folds before printing, and an open fold's content
   grows from 0 over 240ms, so an engine that paints the print layout mid-way
   printed the install extras and the six findings empty (§7.3 C5): on paper
   the content has its own height at once, and the summaries, a phone's
   controls, are not printed. */
@media print {
  .site-header { position: static; }
  /* One column on paper: a printed page has its own columns, and a block
     broken across two of them reads as two blocks. */
  .install__extra { columns: 1; }
  .nav-toggle, .skip-link { display: none; }
  .compact::details-content, .compact[open]::details-content,
  .run__lenses::details-content, .run__lenses[open]::details-content { transition: none; block-size: auto; }
  .compact__summary, .run__summary { display: none; }
}
