/* ============================================================
   tokens.css — fiv4 design tokens
   Source: docs/design-system-v2.md §L (colour system v3 — five gels),
   §C (type), §D (spacing/radius), §F.4 (motion).
   §B (walnut/bone/brass) is SUPERSEDED — see §L.
   No component styles live here — tokens and @font-face only.
   ============================================================ */

/* ---------- fonts: self-hosted, no CDN --------------------- */
@font-face {
  font-family: "Bricolage Grotesque";
  src: url("/fonts/bricolage-grotesque-var.woff2") format("woff2-variations"),
       url("/fonts/bricolage-grotesque-var.woff2") format("woff2");
  font-weight: 200 800;
  font-stretch: 75% 100%;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("/fonts/literata-var.woff2") format("woff2-variations"),
       url("/fonts/literata-var.woff2") format("woff2");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Martian Mono";
  src: url("/fonts/martian-mono-var.woff2") format("woff2-variations"),
       url("/fonts/martian-mono-var.woff2") format("woff2");
  font-weight: 100 800;
  font-stretch: 75% 112.5%;
  font-style: normal;
  font-display: swap;
}

/* ============================================================
   THE FIVE GELS — §L
   Coloured stage gels are how a drum is actually lit, so the
   saturation is physically honest. One hue per beat. The ring
   lights in the colour of the beat currently playing. Each page
   takes one gel as its chapter colour.

     beat 1  magenta    #FF2E88
     beat 2  cyan       #00CFE8
     beat 3  lime       #C7F53D
     beat 4  vermilion  #FF3B1F
     beat 5  violet     #7B3FE4

   The gel hex is the LIGHT ITSELF and never changes between
   themes — a gel is a physical filter, not a theme choice. What
   changes per theme is which ink can be read against it.
   ============================================================ */

/* ---------------------------------------------------------------------------
   EACH GEL HAS THREE ROLES AND THEY NEED THREE TOKENS.

   --beat-N        the gel itself. Lit arcs, fills, glows, graphics.
                   Identical in both themes. NEVER used as body text in
                   the light theme (magenta on pale grey is 3.05:1).
   --beat-N-text   the gel as TEXT on --ground / --pad. In dark this is
                   usually the gel unchanged; violet is the exception
                   (3.44:1 raw) and carries a lightened variant. In light
                   every gel is darkened until it clears AA on the pale
                   ground. Pages must use this token for coloured type
                   rather than improvising a tint.
   --beat-N-on     the ink placed ON a --beat-N fill. Beats 1–4 are bright
                   enough to carry the dark ground as ink; beat 5 (violet)
                   is dark enough that it must carry --ink instead. There
                   is no single value that works for all five.

   This is the same role split the walnut palette needed for brass
   (--accent vs --accent-fill), generalised: a colour used as TEXT and the
   same colour used as a FILL pull in opposite directions, so one value
   cannot serve both. --accent / --accent-fill survive as the CHAPTER
   aliases of that split and remain load-bearing:
     --accent      = the chapter gel as text        (AA on --ground)
     --accent-fill = the chapter gel as a fill      (carries --chapter-on)
   Do NOT "fix" a fill to var(--accent) or a text colour to
   var(--accent-fill) — either swap breaks AA in one of the two themes.
   --------------------------------------------------------------------------- */

/* ---------- 1. bare :root = complete DARK palette ---------- */
:root {
  /* ---- the five gels (theme-invariant) ---- */
  --beat-1: #FF2E88;   /* magenta   */
  --beat-2: #00CFE8;   /* cyan      */
  --beat-3: #C7F53D;   /* lime      */
  --beat-4: #FF3B1F;   /* vermilion */
  --beat-5: #7B3FE4;   /* violet    */

  /* ---- gel as text, dark theme ---- */
  --beat-1-text: #FF2E88;  /*  5.62:1 on --ground */
  --beat-2-text: #00CFE8;  /* 10.40:1 */
  --beat-3-text: #C7F53D;  /* 15.53:1 */
  --beat-4-text: #FF3B1F;  /*  5.53:1 */
  /* violet raw is 3.44:1 on --ground — the one gel that cannot be its own
     text colour on a dark stage. Lightened along the same hue (262deg) to
     #B79BFF = 8.58:1. Pages must not invent their own violet tint. */
  --beat-5-text: #B79BFF;

  /* ---- ink placed ON a gel fill (theme-invariant: the fill is) ---- */
  --beat-1-on: #0B0A12;  /*  5.62:1 */
  --beat-2-on: #0B0A12;  /* 10.40:1 */
  --beat-3-on: #0B0A12;  /* 15.53:1 */
  --beat-4-on: #0B0A12;  /*  5.53:1 */
  --beat-5-on: #F2F4F8;  /*  5.19:1 — violet is too dark for dark ink */

  /* ---- chapter colour: each page overrides these three (see §L) ---- */
  --chapter:      var(--beat-1);
  --chapter-text: var(--beat-1-text);
  --chapter-on:   var(--beat-1-on);

  /* ---- semantic roles ---- */
  --ground:   #0B0A12;  /* the dark stage — blue-black, NOT walnut */
  --ink:      #F2F4F8;  /* cool white, NOT bone            17.88:1 */
  --quiet:    #9AA3B8;  /* cool grey secondary voice        7.79:1 */
  --edge:     #2A2740;  /* borders, hairlines, unlit arcs — never text */
  --pad:      #151322;  /* raised panel: ground lifted toward violet */
  --accent:      var(--chapter-text);
  --accent-fill: var(--chapter);
  --accent-2:    var(--beat-2-text); /* the standing second voice: cyan */

  /* legacy alias — every component should migrate to --quiet */
  --ink-quiet: var(--quiet);

  /* ---- material ground, §B.3 + §K.4 ----
     The blend mode is a token because it is theme-dependent: multiply over
     a near-black ground is close to a no-op (the §K.4 finding), so the dark
     theme screens the texture in and the light theme multiplies it in. */
  --ground-blend: screen;
  --ground-opacity: 0.34;
  --grain-opacity: 0.05;

  /* ---- semantic status colours — never borrow the gels ---- */
  --ok:   #24E08A;  /* 11.35:1 on --ground */
  --warn: #FFC400;  /* 12.33:1 */
  --stop: #FF4368;  /*  5.87:1 */

  /* ------------------------------------------------------------------
     DEPRECATED walnut-era aliases (§B). Kept ONLY so pages still render
     while they are repainted against §L. Every one of these is scheduled
     for deletion — do not introduce new uses. There is no walnut, bone,
     brass, patina or felt in this system any more; these names now point
     at their nearest v3 role.
     ------------------------------------------------------------------ */
  --shell:  var(--ground);
  --head:   var(--ink);
  --chrome: var(--quiet);
  --felt:   var(--edge);
  --brass:  var(--accent);        /* text-safe: fills must use --accent-fill */
  --patina: var(--beat-2-text);

  /* ---------- type families ---------- */
  --font-display: "Bricolage Grotesque", sans-serif;
  --font-body:    "Literata", serif;
  --font-mono:    "Martian Mono", monospace;

  /* ---------- type scale (base 17px, ratio 1.25), §C.2 ---------- */
  --t-2-size: 0.6875rem; --t-2-lh: 1.45; --t-2-track:  0.09em;  --t-2-weight: 600;
  --t-1-size: 0.875rem;  --t-1-lh: 1.45; --t-1-track:  0.04em;  --t-1-weight: 400;
  --t0-size:  1.0625rem; --t0-lh:  1.60; --t0-track:   0em;     --t0-weight:  400;
  --t1-size:  1.3125rem; --t1-lh:  1.55; --t1-track:   0em;     --t1-weight:  400;
  --t2-size:  1.6875rem; --t2-lh:  1.20; --t2-track: -0.010em;  --t2-weight:  500;
  --t3-size:  2.0625rem; --t3-lh:  1.18; --t3-track: -0.014em;  --t3-weight:  500;
  --t4-size:  2.625rem;  --t4-lh:  0.94; --t4-track: -0.020em;  --t4-weight:  700;
  --t5-size:  3.25rem;   --t5-lh:  0.94; --t5-track: -0.028em;  --t5-weight:  700;
  --t6-size:  4.0625rem; --t6-lh:  0.94; --t6-track: -0.028em;  --t6-weight:  700;
  --t7-size:  5.0625rem; --t7-lh:  0.92; --t7-track: -0.035em;  --t7-weight:  700;
  --t8-size:  6.3125rem; --t8-lh:  0.92; --t8-track: -0.035em;  --t8-weight:  700;
  --t9-size:  7.9375rem; --t9-lh:  0.90; --t9-track: -0.040em;  --t9-weight:  700;

  --display-threshold: 2.625rem; /* t4 — 42px */
  --measure: 66ch;

  /* ---------- spacing scale — the only permitted gaps, §D ---------- */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 16px;
  --sp-4: 24px;
  --sp-5: 40px;
  --sp-6: 64px;
  --sp-7: 96px;
  --sp-8: 152px;

  --stage-max: 1272px;
  --gutter: 24px;
  --section-gap: 96px;

  /* ---------- radius vocabulary — exactly three values, §D ---------- */
  --r-1: 2px;
  --r-2: 12px;
  --r-3: 999px;

  /* ---------- motion tokens — one easing, four durations, §F.4 ---------- */
  --ease:    cubic-bezier(.2,.7,.3,1);
  --t-tick:  90ms;
  --t-hover: 160ms;
  --t-move:  380ms;
  --t-scene: 900ms;
}

/* ============================================================
   THE LIGHT THEME IS A LIT STAGE, NOT A PAPER ONE.
   Pale COOL greys only — the house lights came up on the same
   stage; the gels are still in the lanterns and still at full
   chroma. There is no cream, bone, tan or warm neutral anywhere
   in this block, and there must never be: any value whose blue
   channel falls below its red channel is a bug.
   Only the TEXT variants move; --beat-N itself never does.
   ============================================================ */

/* ---------- 2. system "light" with no stamp — tokens only ---------- */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --ground: #EDEFF5;
    --ink:    #14161E;  /* 15.70:1 */
    --quiet:  #4E5665;  /*  6.42:1 */
    --edge:   #C1C8D6;
    --pad:    #E1E5EE;

    /* gels darkened along their own hue until they clear AA on pale grey */
    --beat-1-text: #B4005A;  /* 5.91:1 on --ground */
    --beat-2-text: #00646F;  /* 5.98:1 */
    --beat-3-text: #4B6300;  /* 5.92:1 */
    --beat-4-text: #BC1B00;  /* 5.54:1 */
    --beat-5-text: #5B21B6;  /* 7.81:1 */

    --ground-blend: multiply;
    --ground-opacity: 0.26;
    --grain-opacity: 0.04;

    --ok:   #00704A;  /* 5.35:1 */
    --warn: #7A5200;  /* 6.02:1 */
    --stop: #C2003A;  /* 5.44:1 */
  }
}

/* ---------- 3. explicit light stamp wins over a dark OS ---------- */
:root[data-theme="light"] {
  --ground: #EDEFF5;
  --ink:    #14161E;
  --quiet:  #4E5665;
  --edge:   #C1C8D6;
  --pad:    #E1E5EE;

  --beat-1-text: #B4005A;
  --beat-2-text: #00646F;
  --beat-3-text: #4B6300;
  --beat-4-text: #BC1B00;
  --beat-5-text: #5B21B6;

  --ground-blend: multiply;
  --ground-opacity: 0.26;
  --grain-opacity: 0.04;

  --ok:   #00704A;
  --warn: #7A5200;
  --stop: #C2003A;
}

/* ============================================================
   THE CHAPTER RULE — §L
   The five instrument pages take the five gels in beat order, so
   moving through the site walks the bar. The two reading pages
   (/how, /about) do not own a beat; they borrow one and say so.
   A page may re-declare these three on its own root element, but
   it must set all three together and it must take an existing
   gel — no page invents a sixth colour.
   ============================================================ */
body[data-page="index"]    { --chapter: var(--beat-1); --chapter-text: var(--beat-1-text); --chapter-on: var(--beat-1-on); }
body[data-page="five"]     { --chapter: var(--beat-2); --chapter-text: var(--beat-2-text); --chapter-on: var(--beat-2-on); }
body[data-page="patterns"] { --chapter: var(--beat-3); --chapter-text: var(--beat-3-text); --chapter-on: var(--beat-3-on); }
body[data-page="kit"]      { --chapter: var(--beat-4); --chapter-text: var(--beat-4-text); --chapter-on: var(--beat-4-on); }
body[data-page="loop"]     { --chapter: var(--beat-5); --chapter-text: var(--beat-5-text); --chapter-on: var(--beat-5-on); }
/* borrowed, not owned */
body[data-page="how"]      { --chapter: var(--beat-2); --chapter-text: var(--beat-2-text); --chapter-on: var(--beat-2-on); }
body[data-page="about"]    { --chapter: var(--beat-1); --chapter-text: var(--beat-1-text); --chapter-on: var(--beat-1-on); }

/* ------------------------------------------------------------------
   MEASURED, NOT ASSUMED: a custom property containing var() is
   substituted at the element where it is DECLARED, not where it is
   used. --accent: var(--chapter-text) declared on :root therefore
   freezes to :root's chapter (beat 1) and every page rendered magenta
   chrome regardless of its data-page rule — verified in the browser
   before this rule existed. The aliases must be re-declared on body,
   below the chapter rules, so they resolve against body's --chapter.
   The :root copies above stay as the pre-body default.
   ------------------------------------------------------------------ */
body {
  --accent:      var(--chapter-text);
  --accent-fill: var(--chapter);
  --brass:       var(--accent);
}

body {
  background: var(--ground);
  color: var(--ink);
}

/* ============================================================
   base.css — fiv4 base layer
   Reset, focus, utilities, containers, material-ground,
   heading/body/link/button defaults, prefers-reduced-motion.
   Colour comes entirely from the §L five-gel token set: chrome
   reads in the page's --chapter gel, nothing here names a hex.
   This is the BASE layer: low specificity throughout so page
   CSS (loaded after) can override without !important.
   ============================================================ */

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body, h1, h2, h3, h4, p, figure, blockquote, dl, dd { margin: 0; }
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
body {
  min-height: 100dvh;
  font-family: var(--font-body);
  font-size: var(--t0-size);
  line-height: var(--t0-lh);
  letter-spacing: var(--t0-track);
  font-weight: var(--t0-weight);
}
img, picture, video, canvas, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
ul, ol { list-style: none; padding: 0; }
a { color: inherit; text-decoration: none; }

/* ---------- :focus-visible — chapter-gel focus ring ---------- */
/* --accent (the chapter gel as TEXT) not --accent-fill: the ring sits on
   --ground in both themes, so it needs the AA-safe variant. */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-1);
}

/* ---------- .sr-only ---------- */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- skip link ---------- */
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--sp-3);
  z-index: 1000;
  padding: var(--sp-2) var(--sp-3);
  background: var(--chapter);
  color: var(--chapter-on);
  font-family: var(--font-mono);
  font-size: var(--t-1-size);
  font-weight: 600;
  border-radius: var(--r-1);
  transition: top var(--t-hover) var(--ease);
}
.skip-link:focus {
  top: var(--sp-2);
}

/* ---------- frame / measure containers ---------- */
.frame {
  width: 100%;
  max-width: var(--stage-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
@media (min-width: 760px) {
  .frame { --gutter: 40px; }
}
.measure {
  max-width: var(--measure);
}
.bleed {
  width: 100%;
  position: relative;
}

/* ---------- material-ground utility, §B.3 ---------- */
/* Ground image at 22% multiply + 2% procedural grain, with a graceful
   appearance if the image asset is missing (flat --ground carries on). */
.material-ground {
  position: relative;
  background-color: var(--ground);
  isolation: isolate;
}
.material-ground::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: var(--ground-image, none);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  opacity: var(--ground-opacity, 0.3);
  mix-blend-mode: var(--ground-blend, screen);
  pointer-events: none;
}
.material-ground::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--grain-opacity, 0.05);
  background-image:
    repeating-linear-gradient(0deg, var(--ink) 0px, transparent 1px, transparent 2px),
    repeating-linear-gradient(90deg, var(--ink) 0px, transparent 1px, transparent 2px);
  mix-blend-mode: overlay;
}
.material-ground > * {
  position: relative;
  z-index: 2;
}

/* ---------- heading defaults ---------- */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--t4-weight);
  text-wrap: balance;
}
h1 {
  font-size: clamp(var(--t6-size), 6vw, var(--t8-size));
  line-height: var(--t8-lh);
  letter-spacing: var(--t8-track);
}
h2 {
  font-size: var(--t4-size);
  line-height: var(--t4-lh);
  letter-spacing: var(--t4-track);
  font-weight: var(--t2-weight);
}
h3 {
  font-size: var(--t3-size);
  line-height: var(--t3-lh);
  letter-spacing: var(--t3-track);
  font-weight: var(--t2-weight);
}
h4 {
  font-size: var(--t2-size);
  line-height: var(--t2-lh);
  letter-spacing: var(--t2-track);
  font-weight: var(--t2-weight);
}
p { max-width: var(--measure); }

/* ---------- link styles ---------- */
/* The ink+underline affordance is unchanged and deliberately so: link text
   reads in --ink (17.88:1 dark / 15.70:1 light) and the chapter gel is
   carried by the UNDERLINE, where contrast is not a reading requirement.
   The underline is always present, so the affordance never depends on
   colour alone — which matters more now that the gels are loud enough to
   be mistaken for decoration. Hover thickens the rule and swaps it to the
   standing second voice (cyan), so the change is visible without colour. */
a[href] {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--chapter);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
  transition: color var(--t-hover) var(--ease),
              text-decoration-color var(--t-hover) var(--ease),
              text-decoration-thickness var(--t-hover) var(--ease),
              opacity var(--t-hover) var(--ease);
}
a[href]:hover, a[href]:focus-visible {
  color: var(--ink);
  text-decoration-color: var(--accent-2);
  text-decoration-thickness: 2px;
}
/* Site chrome carries its own affordances (mono uppercase lists, the
   aria-current rule, the wordmark, the gel skip pill) — no underline. */
.site-nav a[href],
.site-footer a[href],
.skip-link[href] {
  text-decoration: none;
}

/* ---------- button / control base ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-3);
  border: 1px solid var(--edge);
  background: var(--pad);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-1-size);
  font-weight: 600;
  letter-spacing: var(--t-1-track);
  text-transform: uppercase;
  transition: background var(--t-hover) var(--ease),
              color var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease),
              transform var(--t-hover) var(--ease);
}
.btn:hover, .btn:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}
.btn:active { transform: scale(0.98); }
.btn[disabled] {
  opacity: 0.5;
  cursor: not-allowed;
}
.btn--accent {
  /* --accent-fill, not --accent: see the role note in tokens.css */
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}
.btn--accent:hover, .btn--accent:focus-visible {
  color: var(--chapter-on);
  opacity: 0.9;
}

/* font-variant-numeric on every mono / tabular readout */
.mono, [data-mono] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ============================================================
   site chrome — .site-nav / .site-footer, §D + §D.7/§G
   Horizontal three-zone bar on every page: ring + wordmark left,
   the seven routes centre/right, a real menu control below 760px.
   Low specificity throughout — page CSS loads after and must be
   able to override without !important.
   ============================================================ */

.site-nav {
  position: relative;
  z-index: 20; /* stays above .material-ground stacking contexts below it in DOM order */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  height: 64px;
  border-bottom: 1px solid var(--edge);
}
.site-nav__home {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  flex: 0 0 auto;
}
.site-nav__wordmark {
  font-family: var(--font-display);
  font-weight: 500;
  font-size: var(--t1-size);
  letter-spacing: var(--t2-track);
  color: var(--ink);
  transition: color var(--t-hover) var(--ease);
}
.site-nav__home:hover .site-nav__wordmark,
.site-nav__home:focus-visible .site-nav__wordmark {
  color: var(--accent);
}

.site-nav__links {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-nav__links li { display: block; }
.site-nav__links a {
  position: relative;
  display: inline-block;
  padding: var(--sp-1) 0;
  font-family: var(--font-mono);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  text-transform: uppercase;
  color: var(--ink-quiet);
  transition: color var(--t-hover) var(--ease);
}
.site-nav__links a:hover,
.site-nav__links a:focus-visible {
  color: var(--ink);
}
.site-nav__links a[aria-current="page"] {
  color: var(--accent);
}
.site-nav__links a[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: -3px;
  height: 2px;
  background: var(--chapter);
  border-radius: var(--r-1);
}

/* mobile menu control — hidden on desktop, a real toggle below 760px */
.site-nav__toggle {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: var(--r-1);
}
.site-nav__toggle-bar {
  width: 20px;
  height: 2px;
  background: var(--ink);
  border-radius: var(--r-1);
  transition: background var(--t-hover) var(--ease);
}
.site-nav__toggle[aria-expanded="true"] .site-nav__toggle-bar:nth-child(2) {
  opacity: 0;
}

@media (max-width: 759px) {
  .site-nav__toggle { display: inline-flex; }
  .site-nav__links {
    position: absolute;
    top: 64px;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0 var(--gutter);
    background: var(--pad);
    border-bottom: 0 solid var(--edge);
    max-height: 0;
    overflow: hidden;
    transition: max-height var(--t-move) var(--ease),
                padding var(--t-move) var(--ease),
                border-bottom-width var(--t-move) var(--ease);
  }
  .site-nav__links[data-open] {
    max-height: 480px;
    padding: var(--sp-2) var(--gutter) var(--sp-3);
    border-bottom-width: 1px;
  }
  .site-nav__links li { width: 100%; }
  .site-nav__links a {
    display: block;
    padding: var(--sp-2) 0;
  }
  .site-nav__links a[aria-current="page"]::after {
    display: none;
  }
  .site-nav__links a[aria-current="page"] {
    border-left: 2px solid var(--chapter);
    padding-left: var(--sp-2);
  }
}

.site-footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  padding-block: var(--sp-6);
  border-top: 1px solid var(--edge);
}
.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-4);
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-footer__links a {
  font-family: var(--font-mono);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  text-transform: uppercase;
  color: var(--ink-quiet);
  transition: color var(--t-hover) var(--ease);
}
.site-footer__links a:hover,
.site-footer__links a:focus-visible {
  color: var(--accent);
}
.site-footer__contact {
  margin-left: auto;
  font-size: var(--t-1-size);
  color: var(--ink-quiet);
}
/* Same rule as the base link: the mailto reads in --ink and wears the
   gel as an underline. */
.site-footer__contact a {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--chapter);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
}
.site-footer__contact a:hover,
.site-footer__contact a:focus-visible {
  color: var(--ink);
  text-decoration-color: var(--accent-2);
  text-decoration-thickness: 2px;
}
@media (max-width: 759px) {
  .site-footer { flex-direction: column; align-items: flex-start; }
  .site-footer__contact { margin-left: 0; }
}

/* ---------- prefers-reduced-motion — a design, not a kill switch, §F.7 ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
  }
  /* Transforms shorten to effectively nothing; colour transitions are kept
     so state changes stay legible (§F.7.5). */
  *, *::before, *::after {
    transition-duration: 1ms, 1ms, var(--t-move), var(--t-move);
    transition-property: transform, outline-offset, color, background-color, fill, border-color, opacity;
  }
}

/* Decorative ambience (drifting ripple, /loop parallax + ground reaction,
   card hover previews, /about sequence) is gated behind this query in JS —
   `matchMedia('(prefers-reduced-motion: no-preference)').matches` — and the
   corresponding classes/animations are declared only under it so nothing is
   animated-then-hidden; it simply never mounts. Individual page CSS may add
   rules inside this block. */
@media (prefers-reduced-motion: no-preference) {
  :root { --motion-ok: 1; }
}

/* ============================================================
   about.css — `/about` What fiv4 is
   Scoped under .about. Tokens only: see tokens.css.
   Source: docs/design-system-v2.md §D.7, §E, §F.6, §F.7, §G,
   §H, the §K amendments (K.1 scale, K.2 contrast, K.3 no
   clipping, K.4 measured ground material) and §L (colour v3).

   COLOUR: repainted against §L. /about does not own a beat — it
   BORROWS beat 1, magenta #FF2E88 (tokens.css sets the three
   chapter tokens from body[data-page="about"]). A reading page,
   so the gel appears only on rules, links, the arc glyphs and the
   eyebrows; the fold's chroma is the photograph, which is a real
   cyan-gel generation (assets.json about-room, seed 303), not a
   tint over a warm frame.

   Every walnut-era alias is gone from this file:

     --ink-quiet, --chrome  -> --quiet        (secondary voice, §L.3)
     --shell                -> --chapter-on   (theme-invariant stage)
     --head                 -> --beat-5-on    (theme-invariant light ink)
     --felt                 -> --edge         (hairlines, unlit arcs)
     --brass                -> --beat-N       per row: an arc lights in
                               the gel of the beat it is (§L.6)
     --patina               -> --chapter      for "you are here" (§L.5)

   §L.4: a fill is never var(--accent) and text is never
   var(--accent-fill). Raw magenta on the pale ground is 3.05:1 and
   cannot carry type; --accent (#B4005A in light) can.
   ============================================================ */

.about { display: block; }

/* ---------- 2. Hero / the fold ---------- */
/* §K.4: no `ground-shell` anywhere on this page (defect #2 — it was
   blurred flat in post and measures near-zero variance). The fold is
   carried by about-room.webp itself, which is real photographic
   material: one hard cyan gel, a long shadow, a tiled floor.
   Lever (d) of K.4 — let the real asset occupy the whole fold.

   The stage under it is --chapter-on, not --ground. --ground is
   theme-dependent and the photograph is not, so a --ground stage
   flashed pale grey for the 350ms load delay and swallowed the light
   ink sitting on it. --chapter-on is declared once in the bare :root
   and never redefined by a theme block, so it is #0B0A12 in both
   themes by construction (§L.4) and matches the room's own falloff.
   It sits UNDER the photograph, full bleed, no edge: not a scrim. */
.about__hero {
  position: relative;
  overflow: hidden;
  /* §D.1: the nav is 64px. Subtracting it means nav + fold fill exactly
     one viewport, so §K.3 can be satisfied by construction. */
  min-height: calc(100dvh - 64px);
  display: flex;
  background-color: var(--chapter-on);
}

.about__hero-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Frames the room so the lit floor and the drum sit in the lower
     third and the deep falloff sits behind the type — §K.2 fix (b). */
  object-position: 50% 64%;
  z-index: 0;
  /* §K.2 fix (c) — a grade applied to THE ASSET, which the amendment
     sanctions doing in post and which is done here at render time
     because the file is shared with /how's ledger and must not change
     on disk. It is a filter on the <img> element: there is no overlay,
     no panel and no rectangle, which is the thing defect #12 was about.

     Measured, not guessed. The cyan beam enters top-centre and the
     headline's first line crosses it: 28.7% of the h1's inked pixels
     sat on background luminance above 0.16, bottoming out at 1.79:1
     against the light ink — a real §K.2 failure across a third of the
     headline, not one stray pixel. Neither ink can carry that on its
     own (dark ink measures 1.18:1 on the room's falloff, which is most
     of the fold), and there is no legal way to change ink mid-glyph.
     Turning the lantern down is the material answer: at brightness 0.58
     the worst single pixel under the headline is 4.70:1 and the
     5th-percentile-worst 16px tile is 5.44:1.
     The saturate(1.12) is not decoration — brightness alone costs a
     little chroma, and the compensation puts the fold's mean HSV
     saturation at 209.6 against 203.0 ungraded, with warm-hue coverage
     still 0.00%. It reads as less lamp, not as less photograph. */
  filter: brightness(0.58) saturate(1.12);
  opacity: 0;
  animation: about-hero-fade var(--t-scene) linear forwards;
  animation-delay: 350ms;
}
@keyframes about-hero-fade {
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .about__hero-img { opacity: 1; animation: none; }
}

/* §K.3: an explicit two-row grid, aligned to the top and bottom of one
   viewport. The headline takes the full width in row 1; the statement
   and the ring share row 2, so the ring never has to be stacked below
   the copy and can never be pushed past the fold's bottom edge.

   §K.2 / THE INK IS THEME-INVARIANT, and this is the defect the v3
   repaint had to fix. The old rule set this block in `--head`, which
   the deprecated alias layer resolves to `--ink` — near-white on dark
   and near-BLACK on light. Against an opaque photograph that does not
   flip (defect #6), the light theme therefore painted the headline and
   the whole statement in #14161E on a near-black room: measured under
   2:1, effectively invisible. --beat-5-on is the system's ink for
   exactly this case — the light ink for a gel too dark to carry dark
   ink — and like every --beat-N-on it is declared only in the bare
   :root, so it is #F2F4F8 in BOTH themes. Same token, same reasoning,
   as /five's hero copy.
   No card, no scrim, no plate — the falloff of the room is the fix. */
.about__hero-inner {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  /* Content is anchored to the TOP of the fold, not spread across it:
     that keeps every word of the statement inside the room's dark
     falloff (§K.2 fix (b)) and leaves the lit floor, the drum and its
     shadow as an unobstructed band of pure material at the bottom —
     which is also the band measured for §K.4. */
  align-content: start;
  column-gap: var(--sp-5);
  row-gap: var(--sp-6);
  width: 100%;
  min-height: calc(100dvh - 64px);
  padding-block: var(--sp-6);
  color: var(--beat-5-on);
}
.about__hero-inner h1 {
  grid-column: 1 / -1;
  grid-row: 1;
}
.about__statement { grid-column: 1; grid-row: 2; }
.about__hero-ring { grid-column: 2; grid-row: 2; }

.about__hero-inner h1 {
  color: var(--beat-5-on);
  /* §K.1. 11.5vw (not the raw 11vw the amendment quotes) is what this
     exact phrase in this exact face needs to clear the required ≥60vw
     at 1440 — same adjustment `/` and `/kit` made, and still inside
     the 64/168px clamp bounds K.1 fixes. */
  font-size: clamp(64px, 11.5vw, 168px);
  line-height: 0.92;
  letter-spacing: -0.03em;
  text-wrap: wrap;
}
.about__statement {
  align-self: start;
  color: var(--beat-5-on);
  font-size: var(--t0-size);
  line-height: var(--t0-lh);
  /* Held well short of the reading measure on purpose: at 1440 this keeps
     every line clear of the drum's lit head, so the whole statement sits
     on the room's falloff and nothing needs a scrim (§K.2). */
  max-width: 46ch;
}
/* §K.3: the ring is in normal flow at the end of the fold column, so it
   cannot be pushed past the bottom edge at any viewport.
   §D.7: 26vmin, static until hover — about.js swaps in a live ring on
   pointer/focus, and never under reduced motion. */
.about__hero-ring {
  align-self: start;
  justify-self: end;
  /* The ring paints its square, dots and counter from --quiet, --ink and
     --ground, all of which are tuned for a bare page ground and all of
     which flip with the theme. This ring is over the photograph, which
     does not. Rebinding the three tokens LOCALLY — to the two
     theme-invariant inks the gel system already defines — keeps the
     component completely untouched (it still contains no hex and still
     does not know that themes exist) while making its counter and square
     legible in both themes. The lit arcs read --beat-N, which is
     theme-invariant already, so they are not rebound. */
  --ink: var(--beat-5-on);
  --ground: var(--chapter-on);
  --quiet: var(--beat-5-on);
  /* --edge is the one role with no theme-invariant twin in the token set:
     it is defined as "a line that just separates from --ground", and
     --ground moves. Left alone it painted a near-black arc in the dark
     theme and a pale grey one in the light theme, over the same
     photograph. Reconstructed here from the two invariant tokens in the
     same ratio --edge stands to --ground/--ink in the dark palette, so
     it is derived from the system rather than picked: no hex, no
     third-party grey, and identical in both themes like the image it
     sits on. */
  --edge: color-mix(in srgb, var(--beat-5-on) 22%, var(--chapter-on));
}
.about__hero-ring fiv4-ring {
  --ring-size: 26vmin;
}

@media (max-width: 759px) {
  /* Mobile collapse rule (§D): the ring moves above the headline and the
     frame shifts off the light shaft so the type keeps its dark ground. */
  /* A 16:9 photograph in a 9:19 fold can only ever be cropped
     horizontally, so at 390 the lit floor would always land in the
     middle of the statement. Rendering the image 132% of the fold's
     height and clipping the overflow gives the vertical crop instead:
     the copy gets the room's wall for its whole depth, and the drum
     and its floor enter at the very bottom, under the last line. */
  .about__hero-img {
    height: 132%;
    bottom: auto;
    object-position: 51% top;
  }
  /* At 390 the room's dark falloff is only ~600px tall before the lit
     floor begins, and the statement needs ~270px of it. The stack is
     therefore tightened — smaller ring, 16px gaps, 24px padding — so the
     whole fold's copy finishes above the floor line. Measured: the worst
     single inked pixel of the statement is reported in the build report.
     This is §K.2 fix (b) again, not a scrim. */
  .about__hero-inner {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    align-content: start;
    row-gap: var(--sp-3);
    padding-block: var(--sp-4);
  }
  .about__hero-inner h1 { grid-column: 1; grid-row: 2; }
  .about__statement { grid-column: 1; grid-row: 3; align-self: start; }
  .about__hero-ring {
    grid-column: 1;
    grid-row: 1;
    justify-self: center;
  }
  .about__hero-ring fiv4-ring { --ring-size: 26vw; }
}

/* ---------- shared section furniture ---------- */
/* The eyebrows are the page's chapter colour as TEXT: --accent resolves
   to --beat-1-text, 5.62:1 on the dark ground and 5.91:1 on the pale
   one (§L.7). The walnut-era note here recorded patina failing at
   2.92:1 — that token no longer exists, and the gel system's whole
   point is that the text variant is a different value from the fill. */
.about__eyebrow {
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--accent);
}

/* ---------- 3. Every page ---------- */
.about__pages {
  padding-block: var(--section-gap);
}
.about__list {
  margin-top: var(--sp-5);
  border-top: 1px solid var(--edge);
}

.about__link {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) auto;
  column-gap: var(--sp-3);
  row-gap: var(--sp-2);
  align-items: center;
  padding-block: var(--sp-4);
  border-bottom: 1px solid var(--edge);
  color: var(--ink);
  transition: border-color var(--t-hover) var(--ease);
  /* Caught by screenshot: base.css underlines every a[href], and this
     <a> wraps the whole row — so the description, the route and the
     chevron were all being underlined as one 1100px-wide rule per row.
     The decoration moves to the NAME, which is the link's actual label,
     so the row keeps a non-colour affordance (WCAG 1.4.1) without
     striking a line through five lines of prose.
     Note the selector below: base.css's a[href] is (0,1,1) and a bare
     `.about__link { text-decoration: none }` at (0,1,0) loses to it.
     The first attempt at this fix did exactly that and changed nothing
     on screen. */
}
.about .about__link[href] { text-decoration: none; }
.about .about__link .about__name {
  text-decoration: underline;
  /* an underline is a graphic, so it is the raw gel (§L.4) */
  text-decoration-color: var(--accent-fill);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.14em;
  transition: color var(--t-hover) var(--ease),
              text-decoration-thickness var(--t-hover) var(--ease);
}
.about .about__link:hover .about__name,
.about .about__link:focus-visible .about__name {
  text-decoration-thickness: 2px;
}
.about__glyph { grid-column: 1; grid-row: 1; }
.about__name  { grid-column: 2; grid-row: 1; }
.about__chev  { grid-column: 3; grid-row: 1; }
.about__desc  { grid-column: 2 / -1; grid-row: 2; }
.about__route { grid-column: 2 / -1; grid-row: 3; }

.about__glyph {
  /* 44px, not 34: below ~40px the 3-unit arc gaps and the 6-unit stroke
     of §E's geometry stop resolving and the mark reads as a plain
     circle rather than five arcs. */
  width: 44px;
  height: 44px;
  display: block;
}
.about__glyph svg { width: 100%; height: 100%; }
.about__arc {
  stroke: var(--edge);
  transition: stroke var(--t-move) var(--ease);
}
/* §L.6 — THE LIST IS THE BAR. Rows one to five carry arcs one to five
   of the same ring, and under v3 an arc lights in the colour of the
   beat it IS: magenta, cyan, lime, vermilion, violet, in reading order.
   Scrolling the list therefore walks the same hue cycle the nav ring
   walks in a bar, and the site's page order and its colour order are
   the same fact stated twice. Raw --beat-N is correct here: a lit arc
   is a graphic, never text, and the gel hex is theme-invariant.
   The /how and /about rows carry data-arc="-1" and no --lit arc, which
   is why the list reads as five and not seven.

   Static / JS-off / reduced-motion state is the FINISHED state: the
   five arcs are already lit. §F.6's sequence is a reveal of this, never
   a prerequisite for it — about.js resets to unlit only when it is
   actually going to run the sequence. */
.about__row[data-arc="0"] .about__arc--lit { stroke: var(--beat-1); }
.about__row[data-arc="1"] .about__arc--lit { stroke: var(--beat-2); }
.about__row[data-arc="2"] .about__arc--lit { stroke: var(--beat-3); }
.about__row[data-arc="3"] .about__arc--lit { stroke: var(--beat-4); }
.about__row[data-arc="4"] .about__arc--lit { stroke: var(--beat-5); }
.about__list[data-sequence] .about__arc--lit { stroke: var(--edge); }
.about__list[data-sequence] .about__row[data-arc="0"].is-lit .about__arc--lit { stroke: var(--beat-1); }
.about__list[data-sequence] .about__row[data-arc="1"].is-lit .about__arc--lit { stroke: var(--beat-2); }
.about__list[data-sequence] .about__row[data-arc="2"].is-lit .about__arc--lit { stroke: var(--beat-3); }
.about__list[data-sequence] .about__row[data-arc="3"].is-lit .about__arc--lit { stroke: var(--beat-4); }
.about__list[data-sequence] .about__row[data-arc="4"].is-lit .about__arc--lit { stroke: var(--beat-5); }

.about__name {
  font-family: var(--font-display);
  font-size: var(--t2-size);
  line-height: var(--t2-lh);
  letter-spacing: var(--t2-track);
  font-weight: var(--t2-weight);
  color: var(--ink);
  transition: color var(--t-hover) var(--ease);
}
/* Body-weight copy is --ink; the route and the chevron are the quiet
   voice §L.3 describes (7.79:1 dark, 6.42:1 light — the 3.64:1 walnut
   failure recorded as defect #5 was a property of a token that is gone). */
.about__desc {
  font-family: var(--font-body);
  font-size: var(--t0-size);
  line-height: var(--t0-lh);
  color: var(--ink);
  max-width: var(--measure);
}
.about__route {
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  color: var(--quiet);
}
.about__chev {
  color: var(--quiet);
  font-size: var(--t-1-size);
  transition: color var(--t-hover) var(--ease), transform var(--t-hover) var(--ease);
}

/* A hovered row's rule is a graphic -> --accent-fill (the raw gel).
   Its name and chevron are type -> --accent (the text variant). This
   is the §L.4 split in two adjacent declarations; swapping them would
   put 3.05:1 magenta on the pale ground. */
.about__link:hover,
.about__link:focus-visible {
  border-bottom-color: var(--accent-fill);
}
.about__link:hover .about__name,
.about__link:focus-visible .about__name,
.about__link:hover .about__chev,
.about__link:focus-visible .about__chev {
  color: var(--accent);
}
.about__link:hover .about__chev,
.about__link:focus-visible .about__chev {
  transform: translateX(4px);
}
.about__link:hover .about__arc,
.about__link:focus-visible .about__arc {
  stroke: var(--quiet);
}
/* Hover raises the whole glyph to the chapter gel rather than to five
   different hues, so a hovered row reads as one object. */
.about__link:hover .about__arc--lit,
.about__link:focus-visible .about__arc--lit,
.about__list[data-sequence] .about__link:hover .about__arc--lit,
.about__list[data-sequence] .about__link:focus-visible .about__arc--lit {
  stroke: var(--accent-fill);
}

/* "You are here" — §L.5/§L.6: the current page's arc paints in
   --chapter, exactly as the nav ring's current-index arc does, so the
   chapter colour and the "you are here" cue agree everywhere on the
   site. The NAME stays --ink: the cue is the arcs (decorative) plus
   aria-current (real), never colour alone. */
.about__link[aria-current="page"] .about__arc,
.about__list[data-sequence] .about__link[aria-current="page"] .about__arc {
  stroke: var(--chapter);
}

@media (min-width: 760px) {
  .about__link {
    grid-template-columns: 44px minmax(11rem, 15rem) minmax(0, 1fr) 6.5rem auto;
    column-gap: var(--sp-5);
    row-gap: 0;
    padding-block: var(--sp-5);
  }
  .about__name  { grid-column: 2; grid-row: 1; }
  .about__desc  { grid-column: 3; grid-row: 1; }
  .about__route { grid-column: 4; grid-row: 1; text-align: right; }
  .about__chev  { grid-column: 5; grid-row: 1; }
}

/* ---------- 4. Say something ---------- */
.about__say {
  padding-block: 0 var(--section-gap);
}
.about__contact {
  margin-top: var(--sp-4);
  font-family: var(--font-display);
  font-size: var(--t3-size);
  line-height: var(--t3-lh);
  letter-spacing: var(--t3-track);
  font-weight: var(--t2-weight);
  color: var(--ink);
  max-width: none;
}
/* Two classes + the attribute = (0,2,1), above base.css's a[href] at
   (0,1,1). The address is set in --ink and identified by a gel rule
   under it rather than by a coloured foreground: at t3 this is still
   mid-size type, and the underline is a graphic, so it is the one that
   takes --accent-fill. */
.about .about__contact a[href] {
  color: var(--ink);
  text-decoration: underline;
  text-decoration-color: var(--accent-fill);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.12em;
  transition: text-decoration-color var(--t-hover) var(--ease),
              color var(--t-hover) var(--ease);
}
/* Hover moves the glyphs to the chapter gel as TEXT and the rule to the
   standing second voice — cyan, which §L calls the site's second voice
   and which is 10.40:1 dark / 5.98:1 light if it ever carries type. */
.about .about__contact a[href]:hover,
.about .about__contact a[href]:focus-visible {
  color: var(--accent);
  text-decoration-color: var(--beat-2);
}
.about__note {
  margin-top: var(--sp-4);
  color: var(--quiet);
  font-size: var(--t0-size);
}

/* ============================================================
   five.css — /five "Five over four"
   Scoped entirely under .five. Values from tokens.css only.
   Source: docs/design-system-v2.md §D.2, §E, §F, §K, §L.

   COLOUR: repainted against §L (colour system v3, five gels).
   /five is chapter beat 2, cyan #00CFE8 (tokens.css sets
   --chapter/--chapter-text/--chapter-on from body[data-page="five"]).
   Every walnut-era alias (--shell --head --brass --patina --felt
   --chrome --ink-quiet) has been migrated out of this file:

     --chrome, --ink-quiet  -> --quiet      (secondary voice)
     --shell                -> --ground     (the stage)   ... as a surface
     --shell                -> --chapter-on ... as ink on a gel fill
     --head                 -> --ink        (primary voice)
     --felt                 -> --edge       (hairlines, unlit arcs)
     --brass / --patina     -> --chapter    where it is a FILL or a lit
                               graphic, --accent where it is TEXT.
   Per §L.4 a fill is never var(--accent) and text is never
   var(--accent-fill) — that swap breaks AA in one theme or the other.

   Where this page paints five things that ARE the five beats (the
   strip segment marks, the accent dots, the mini-ring arcs) it uses
   --beat-1..--beat-5 directly rather than the chapter gel, per §L.6:
   a lit arc names the beat you are hearing. Those are lit graphics,
   never text, so the raw gel is the correct token in both themes.
   ============================================================ */

.five { display: block; }

.five__eyebrow {
  display: block;
  color: var(--quiet);
  margin-bottom: var(--sp-3);
}

/* ---------- transport bar (Play / Mute / BPM), every page §G ---------- */
.five__transport {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-block: var(--sp-3);
}
.five__bpm { color: var(--quiet); margin-inline-start: auto; }
/* pressed Play is a chapter-gel FILL, so it carries --chapter-on as ink */
.five__play[aria-pressed="true"] {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}
.five__mute[aria-pressed="true"] { color: var(--stop); border-color: var(--stop); }

/* ============================================================
   HERO — §K.2 / §K.4 / defects #11 + #12
   ------------------------------------------------------------
   What was here before, and why it is gone:

   * a `linear-gradient(to bottom, transparent -> --ground)` on
     .five__hero-inner, added as the §K.2 legibility fix. On the pale
     light ground it rendered as a hard-edged pale rectangle behind the
     headline — a translucent card, banned outright by §D (defect #12).
     REMOVED, not softened. There is no background of any kind on the
     hero content wrapper now, and no scrim, tint, veil or panel
     anywhere in this section.

   * `opacity: 0.5` on the ground layer. Half-strength photo composited
     over --ground meant the light theme mixed 50% pale grey into the
     image: that milky wash is a large part of why the fold read as a
     duotone *filter* laid over a photograph rather than as a lit
     object (defect #11). The asset now renders at full strength and
     is identical in both themes — the material is the material
     (defect #6, accepted by design).

   Legibility now comes from the material, per §K.2's preferred fixes
   (b) then (a): the type is placed over the asset's own dark region,
   and the ink is inverted so it reads there. The bottom third of
   five-strip-32 is unlit planking (measured: 99th-percentile pixel
   luminance behind the glyph line-boxes still clears 6:1 against a
   cool-white ink); the plate row keeps the top of the fold, behind
   the ring, where §K.4 samples the ground band.
   ============================================================ */
.five__hero {
  padding-block: var(--sp-7);
  overflow: hidden; /* contains the ground layer's zoom, below */
}
.five__hero.material-ground::before {
  background-image: url("/assets/five/five-strip-32.webp");
  /* full strength: a photograph, not a wash over --ground */
  opacity: 1;
  mix-blend-mode: normal;
  background-size: cover;
  background-position: 50% 100%;
  /* Framing, expressed as a zoom on the bottom-centre of the frame so it
     holds at every viewport aspect (1440x900 and 390x844 both verified):
     the lower half of the plate row lands across the top of the fold and
     the unlit planking lands under the headline. This is a crop, not an
     effect — no colour, contrast or opacity is touched. */
  transform: scale(2);
  transform-origin: 50% 100%;
}
.five__hero-inner {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  position: relative;
  /* §K.2/§D: deliberately no background, no gradient, no scrim. */
  /* The hero photograph is a dark, gel-lit surface that does not flip
     with the theme, so the ink over it must not flip either. --beat-5-on
     is the system's ink for exactly this case — the light ink for a gel
     too dark to carry dark ink — and it is declared once in the bare
     :root and never redefined by a theme block, so it is theme-invariant
     by construction. Using it here keeps the rule that no component
     names a hex or styles the theme directly. */
  color: var(--beat-5-on);
}
.five__hero-top {
  display: flex;
  justify-content: flex-end;
}
/* §K.3: ring lives in its own row above the headline so it can never be
   clipped by the fold and never collides with the (now much larger)
   display type below it. */
.five__ring--hero { --ring-size: clamp(100px, 12vmin, 170px); }

/* §K.1: hero headline must occupy the fold — clamp(64px,11.5vw,168px),
   line-height 0.92, tracking -0.03em, spanning >=60% of viewport width
   at 1440. Supersedes the t7 assignment in §C.2 for this hero only. */
.five__hero-copy h1 {
  font-size: clamp(4rem, 11.5vw, 10.5rem);
  line-height: 0.92;
  letter-spacing: -0.03em;
  max-width: 100%;
  margin-bottom: var(--sp-4);
}
.five__lead {
  /* colour inherited from .five__hero-inner — see the note there.
     §K.2: contrast measured against the actual rendered pixels behind
     this text (not the flat CSS colour) — figures in the build report. */
  max-width: var(--measure);
  font-weight: 600;
}

/* ---------- count it: the one bleed, live-lit strip ---------- */
.five__count { padding-block: var(--sp-7); }
.five__strip {
  position: relative;
  width: 100%;
  aspect-ratio: var(--strip-ratio, 1800 / 1012);
  overflow: hidden;
  background: var(--ground);
}
.five__strip-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity var(--t-scene) var(--ease);
}
.five__strip-img[data-active="true"] { opacity: 1; }

/* The strip container carries the image's exact aspect ratio and the
   image is object-fit:cover, so there is no crop at any width and image
   x maps to container x by a pure percentage. The marks are therefore a
   true overlay: five.js drives --seg-x / --seg-w per mark straight from
   the measured segment boxes in src/js/five-strip.json, per grouping.
   Before this they were an evenly-spaced row in the 1272 frame under a
   full-bleed photo, which lined up with nothing. */
.five__strip-marks {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(12px, 3.2%, 40px);
  height: 8px;
}
.five__mark {
  position: absolute;
  bottom: 0;
  left: var(--seg-x, 0%);
  width: var(--seg-w, 18%);
  height: 4px;
  border-radius: var(--r-1);
  /* unlit = --quiet, not --edge. §L.3 gives --quiet the "scale marks" role
     and these are scale marks; --edge is sized to disappear against
     --ground, and against a photograph it disappears completely
     (verified on the rendered strip). */
  background: var(--quiet);
  transition: background var(--t-tick) var(--ease), height var(--t-move) var(--ease);
}
/* §L.6 — a lit mark names the beat that is sounding, so each of the five
   takes its own gel rather than one chapter colour. Lit graphics on a
   photograph: the raw gel is correct and theme-invariant. */
.five__mark[data-mark="0"][data-on="true"] { background: var(--beat-1); }
.five__mark[data-mark="1"][data-on="true"] { background: var(--beat-2); }
.five__mark[data-mark="2"][data-on="true"] { background: var(--beat-3); }
.five__mark[data-mark="3"][data-on="true"] { background: var(--beat-4); }
.five__mark[data-mark="4"][data-on="true"] { background: var(--beat-5); }
.five__mark[data-accent="true"] { height: 8px; }

/* ---------- three-then-two / two-then-three ---------- */
.five__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-6);
  padding-block: var(--sp-7);
}
.five__grouping { display: flex; flex-direction: column; gap: var(--sp-3); }
.five__dots { display: flex; gap: var(--sp-2); }
.five__dot {
  width: 14px;
  height: 14px;
  border-radius: var(--r-3);
  background: transparent;
  border: 2px solid var(--edge);
}
/* the dots are the five beats too — same rule as the marks (§L.6) */
.five__dot:nth-child(1).five__dot--on { background: var(--beat-1); border-color: var(--beat-1); }
.five__dot:nth-child(2).five__dot--on { background: var(--beat-2); border-color: var(--beat-2); }
.five__dot:nth-child(3).five__dot--on { background: var(--beat-3); border-color: var(--beat-3); }
.five__dot:nth-child(4).five__dot--on { background: var(--beat-4); border-color: var(--beat-4); }
.five__dot:nth-child(5).five__dot--on { background: var(--beat-5); border-color: var(--beat-5); }
.five__demo {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  margin-top: var(--sp-3);
}
.five__ring--section { --ring-size: clamp(96px, 18vmin, 180px); }
.five__grouping [data-play-section][aria-pressed="true"] {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}

/* ---------- both at once ---------- */
.five__both { padding-block: var(--sp-7); }
.five__both-rings {
  display: flex;
  gap: var(--sp-6);
  align-items: center;
  margin-bottom: var(--sp-4);
}
.five__mini {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-3);
  border-radius: var(--r-2);
  border: 1px solid var(--edge);
  transition: border-color var(--t-hover) var(--ease), box-shadow var(--t-hover) var(--ease);
}
.five__mini[data-live="true"] {
  border-color: var(--chapter);
  box-shadow: 0 0 0 1px var(--chapter);
}
.five__mini-svg { width: clamp(88px, 16vmin, 150px); height: clamp(88px, 16vmin, 150px); }
.five__mini-arc { fill: none; stroke: var(--edge); stroke-width: 6; stroke-linecap: round; transition: stroke var(--t-move) var(--ease); }
/* §L.6 again: arc i lights in beat i+1's gel, so the two mini rings run
   magenta -> cyan -> lime -> vermilion -> violet exactly like <fiv4-ring>. */
.five__mini-arc[data-arc="0"][data-on="true"] { stroke: var(--beat-1); }
.five__mini-arc[data-arc="1"][data-on="true"] { stroke: var(--beat-2); }
.five__mini-arc[data-arc="2"][data-on="true"] { stroke: var(--beat-3); }
.five__mini-arc[data-arc="3"][data-on="true"] { stroke: var(--beat-4); }
.five__mini-arc[data-arc="4"][data-on="true"] { stroke: var(--beat-5); }
.five__mini-square { fill: none; stroke: var(--quiet); stroke-width: 2; transform-origin: 60px 60px; }
.five__mini-pip { fill: var(--chapter); }
.five__ab { display: flex; gap: var(--sp-2); }
.five__ab [data-ab][aria-pressed="true"] {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}

/* ---------- where you've heard it ---------- */
.five__heard { padding-block: var(--sp-7) var(--sp-8); }

/* ---------- mobile collapse (<760px), one rule everywhere §D ---------- */
@media (max-width: 759px) {
  .five__hero-top { justify-content: center; }
  .five__ring--hero { --ring-size: 62vw; }
  .five__grid { grid-template-columns: 1fr; }
  .five__both-rings { flex-direction: column; }
  .five__transport { flex-wrap: wrap; }
  .five__strip-marks { height: 6px; }
  .five__mark { height: 3px; }
  .five__mark[data-accent="true"] { height: 6px; }
}

/* ---------- §F.7 reduced motion is a design, not a kill switch ----------
   The strip cross-fade and the mark transitions are the page's only
   scene-length moves; under reduce they become instant state changes,
   the marks still light on every beat and the accent marks still stand
   proud, so the 3+2 / 2+3 argument is fully readable with no animation. */
@media (prefers-reduced-motion: reduce) {
  .five__strip-img { transition: none; }
  .five__mark { transition: none; }
  .five__mini-arc { transition: none; }
}

/* ============================================================
   how.css — `/how` How it's built
   Scoped under .how. Tokens only: see tokens.css.
   Source: docs/design-system-v2.md §D.6, §H (/how, 7 sections),
   §F.5 (load), §F.6 (the window), §F.7 (reduced motion),
   §K.1–K.4 (director's amendment), §L (colour system v3).

   COLOUR: repainted against §L. /how does not own a beat — it
   BORROWS beat 2, cyan #00CFE8 (tokens.css sets the three chapter
   tokens from body[data-page="how"]). This is a reading page, so
   the gel is spent on rules, links and marks and never floods a
   surface: the only large chroma on the page is the photograph at
   the top, which is a real gel-lit generation, not a tint.

   Every walnut-era alias is gone from this file:

     --ink-quiet, --chrome  -> --quiet        (secondary voice, §L.3)
     --shell                -> --chapter-on   (see the bench note)
     --head                 -> --beat-5-on    (see the bench note)
     --felt                 -> --edge         (hairlines)
     --brass / --patina     -> --accent-fill  where it is a rule, a
                               fill or a lit mark; --accent where it
                               is TEXT.

   §L.4, stated once so it is not re-litigated: a fill is never
   var(--accent) and text is never var(--accent-fill). On the pale
   ground raw cyan is 3.10:1 and cannot carry type; in the dark
   theme --accent IS the raw gel, so the two tokens only visibly
   diverge in light — which is exactly when the mistake ships.
   ============================================================ */

.how { display: block; }

/* ---------- 1. Hero — the one bleed, then the headline ---------- */
.how__hero {
  display: block;
  padding-bottom: var(--sp-6);
}

/* The single full-bleed element of this section (§D one-bleed rule).
   K.4: the fold's ground band is this photograph at full strength —
   ground-shell is not used anywhere on this page (see §"What failed").

   THE STAGE UNDER THE PHOTOGRAPH IS THEME-INVARIANT, and it has to be.
   how-bench.webp is one file served to both themes: a near-black bench
   under a magenta key and a cyan edge light. --shell used to resolve to
   --ground here, which is #EDEFF5 in the light theme — so for the 350ms
   before the image arrives the band flashed pale grey, and any light ink
   sitting on it was invisible. --chapter-on is the token that means "the
   dark ink this page's gel carries"; it is declared once in the bare
   :root and never redefined by a theme block, so it is #0B0A12 in both
   themes by construction (§L.4) and matches the photograph's own falloff.
   It is not a scrim: it sits UNDER the image, full bleed, with no edge. */
.how__bench {
  position: relative;
  width: 100%;
  height: clamp(240px, 38vh, 380px);
  overflow: hidden;
  background-color: var(--chapter-on);
}
.how__bench-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 72%;
  opacity: 0;
  animation: how-bench-fade var(--t-scene) linear forwards;
  animation-delay: 350ms;
}
@keyframes how-bench-fade { to { opacity: 1; } }

/* K.2: the caption sits on the photograph's own dark falloff — no card,
   no scrim, no gradient. The bottom-right of this crop is unlit bench in
   both themes (the photograph does not flip, defect #6), so the ink over
   it must not flip either. --beat-5-on is the system's ink for exactly
   this case — the light ink for a gel too dark to carry dark ink — and
   like every --beat-N-on it is declared only in the bare :root, so it is
   #F2F4F8 in both themes. This is the same token /five uses over its own
   dark gel photograph, for the same reason.
   The left corner was measured first and rejected: the magenta-lit head
   crosses it and drops the worst pixel well under AA. */
.how__bench-caption {
  position: absolute;
  right: var(--gutter);
  bottom: var(--sp-3);
  margin: 0;
  max-width: min(40ch, calc(100% - var(--gutter) * 2));
  text-align: right;
  color: var(--beat-5-on);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
}
/* Two classes, deliberately: base.css sets `a[href] { color: var(--ink) }`
   at (0,1,1), which outranks a single-class page rule at (0,1,0) — the bug
   that made a control invisible on `/`. `.how .how__bench-caption a[href]`
   is (0,3,1) and actually lands. The link keeps the caption's own ink in
   both the glyphs and the underline: no gel on this photograph clears AA
   against the brightest pixel the caption can reach. */
.how .how__bench-caption a[href],
.how .how__bench-caption a[href]:hover,
.how .how__bench-caption a[href]:focus-visible {
  color: var(--beat-5-on);
  text-decoration-color: var(--beat-5-on);
}

.how__hero-copy {
  position: relative;
  padding-top: var(--sp-5);
}

/* The page is long and mostly reading; the fold carries its own index.
   Placed against the right edge, below the short second line of the
   headline, so it never collides with it (§K.3 — nothing clipped).
   This is the page's one piece of coloured type: the borrowed cyan as
   --accent, which is the gel's TEXT variant and measures 10.40:1 on the
   dark ground and 5.98:1 on the pale one (§L.7). Scoped to two classes
   plus the attribute so it outranks base.css's a[href]. */
.how__index {
  margin-top: var(--sp-5);
  font-size: var(--t-2-size);
  line-height: 1.9;
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
}
.how__index ol { display: grid; gap: var(--sp-1); }
.how .how__index a[href] {
  display: inline-flex;
  gap: var(--sp-2);
  color: var(--accent);
  text-decoration-color: transparent;
}
.how .how__index a[href]:hover,
.how .how__index a[href]:focus-visible {
  color: var(--accent);
  text-decoration-color: var(--accent-fill);
}
@media (min-width: 1100px) {
  .how__index {
    position: absolute;
    right: var(--gutter);
    bottom: 0;
    margin-top: 0;
    text-align: right;
  }
  .how__index ol { justify-items: end; }
}
/* §K.1 — hero display type must occupy the fold. 11.5vw is the term the
   other pages settled on for this typeface; measured span is reported
   in the build report. Reading page, but the amendment is binding. */
.how__hero-copy h1 {
  font-size: clamp(64px, 11.5vw, 168px);
  line-height: 0.92;
  letter-spacing: -0.03em;
  text-wrap: pretty;
  color: var(--ink);
}
.how__hero-sub {
  margin-top: var(--sp-4);
  max-width: 62ch;
  color: var(--ink);
}

/* ---------- 2. Section rhythm ---------- */
.how__section {
  padding-block: var(--section-gap);
  border-top: 1px solid var(--edge);
}
.how__section--last { border-bottom: 1px solid var(--edge); }
@media (min-width: 760px) {
  .how__section { padding-block: var(--sp-8); }
}
/* The eyebrow's rule is a GRAPHIC, so it takes --accent-fill (the raw
   gel, identical in both themes) rather than --accent. The label itself
   is type, so it takes --accent. That pairing — gel rule, gel text, one
   token each — is the whole of this page's chapter colour. */
.how__eyebrow {
  margin: 0 0 var(--sp-3);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--accent);
  padding-left: var(--sp-2);
  border-left: 2px solid var(--accent-fill);
}
.how__section h2 { margin-bottom: var(--sp-4); }
.how__subhead {
  margin-top: var(--sp-7);
  margin-bottom: var(--sp-3);
}
.how__lede {
  max-width: 60ch;
  margin-bottom: var(--sp-4);
}
.how p { margin-bottom: var(--sp-3); }
.how p:last-child { margin-bottom: 0; }
.how code {
  font-family: var(--font-mono);
  font-size: 0.86em;
  color: var(--ink);
  background: var(--pad);
  border-radius: var(--r-1);
  padding: 1px 4px;
}
/* Body links are left to base.css: --ink glyphs (17.88:1 dark, 15.70:1
   light) with --chapter in the underline. Nothing in the prose overrides
   that, so no link on this page can lose to the (0,1,1) base rule. */

/* ---------- 3. The clock and the scheduler ---------- */
.how__split {
  display: grid;
  gap: var(--sp-6);
  align-items: start;
}
@media (min-width: 1000px) {
  .how__split { grid-template-columns: minmax(0, 1fr) minmax(0, 0.78fr); gap: var(--sp-7); }
  .how__diagram { position: sticky; top: var(--sp-4); }
}
.how__prose { max-width: var(--measure); }
.how__prose h3 { margin-top: var(--sp-5); margin-bottom: var(--sp-2); }

.how__diagram-title {
  margin-bottom: var(--sp-3);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--quiet);
}
.how__track {
  position: relative;
  height: 96px;
  border: 1px solid var(--edge);
  border-radius: var(--r-1);
  overflow: hidden;
  background: var(--pad);
}
.how__cells {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  height: 100%;
  margin: 0;
}
.how__cell {
  position: relative;
  display: flex;
  align-items: flex-end;
  padding: var(--sp-2);
  border-right: 1px solid var(--edge);
  color: var(--quiet);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
}
.how__cell:last-child { border-right: 0; }
/* §L.6 — this diagram IS the bar, so a lit cell names the beat that is
   sounding and takes that beat's own gel, exactly like the ring's arcs
   and /five's segment marks. One hue per beat, not one chapter colour
   across five. The wash is a low-alpha mix over --pad, so the cell's
   own numeral keeps a token-decided contrast rather than an ink over an
   unpredictable fill; --beat-N is used raw because a lit mark is a
   graphic and the gel hex is theme-invariant by design. */
.how__cell[data-armed] { background: color-mix(in srgb, var(--accent-fill) 10%, transparent); }
.how__cell[data-cell="0"][data-on] { background: color-mix(in srgb, var(--beat-1) 22%, transparent); }
.how__cell[data-cell="1"][data-on] { background: color-mix(in srgb, var(--beat-2) 22%, transparent); }
.how__cell[data-cell="2"][data-on] { background: color-mix(in srgb, var(--beat-3) 22%, transparent); }
.how__cell[data-cell="3"][data-on] { background: color-mix(in srgb, var(--beat-4) 22%, transparent); }
.how__cell[data-cell="4"][data-on] { background: color-mix(in srgb, var(--beat-5) 22%, transparent); }
.how__cell span { transition: color var(--t-move) var(--ease); }
.how__cell[data-on] span { color: var(--ink); font-weight: 700; }

/* the 100ms schedule-ahead window, drawn at true scale. A fill and a
   border — both graphics — so both take --accent-fill. */
.how__window {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3.2%;
  background: color-mix(in srgb, var(--accent-fill) 62%, transparent);
  border-inline: 1px solid var(--accent-fill);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent-fill) 40%, transparent);
  pointer-events: none;
}
.how__playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 2px;
  background: var(--ink);
  pointer-events: none;
}
.how__diagram-note {
  margin-top: var(--sp-3);
  font-size: var(--t-1-size);
  line-height: var(--t-1-lh);
  max-width: 52ch;
}

.how__readout {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-1) var(--sp-3);
  margin: var(--sp-4) 0 0;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--edge);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
}
/* §L.3 gives --quiet the "mono data" role, and tokens.css has since
   repaired it (7.79:1 dark, 6.42:1 light) — the 3.64:1 failure that
   forced this page onto --ink everywhere was defect #5 against the
   walnut token, and it no longer exists. The label is the quiet voice,
   the value is the loud one, which is what a readout wants. */
.how__readout dt { text-transform: uppercase; font-weight: var(--t-2-weight); color: var(--quiet); }
.how__readout dd { margin: 0; color: var(--ink); }

.how__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin-top: var(--sp-4);
}
.how__ring { margin-left: auto; }

/* ---------- 4. Every image and the prompt that made it ---------- */
.how__assets { margin: var(--sp-6) 0 0; }
.how__asset {
  display: grid;
  gap: var(--sp-4);
  padding-block: var(--sp-6);
  border-top: 1px solid var(--edge);
}
@media (min-width: 900px) {
  .how__asset {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: var(--sp-5);
  }
  /* the assets are material on this page — shown large, not thumbnailed.
     align-self:start keeps the figure content-sized so sticky has room. */
  .how__asset-fig { position: sticky; top: var(--sp-5); align-self: start; }
}
.how__asset-fig { margin: 0; }
.how__asset-img {
  width: 100%;
  height: auto;
  border-radius: var(--r-2);
  background: var(--pad);
}
.how__asset-shown {
  margin-top: var(--sp-2);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  color: var(--quiet);
}
.how__asset-id {
  font-family: var(--font-mono);
  font-size: var(--t2-size);
  line-height: var(--t2-lh);
  letter-spacing: var(--t2-track);
  font-weight: 600;
}
.how__asset-meta {
  margin-top: var(--sp-2);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  color: var(--quiet);
}
.how__flag {
  display: inline-block;
  margin-top: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid var(--accent-fill);
  border-radius: var(--r-1);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--ink);
}
.how__flag--stop { border-color: var(--stop); color: var(--stop); }
/* A disclosure is not a failure and not a win, so it takes neither
   --stop nor the chapter gel: --warn is the system's own "read this"
   status colour (12.33:1 dark, 6.02:1 light) and §L.3 reserves it for
   exactly this, never borrowing a beat. */
.how__flag--warn { border-color: var(--warn); color: var(--warn); }

.how__prompt {
  margin: var(--sp-3) 0 0;
  padding-left: var(--sp-3);
  border-left: 2px solid var(--accent-fill);
}
.how__prompt p {
  max-width: 62ch;
  font-size: var(--t1-size);
  line-height: var(--t1-lh);
  color: var(--ink);
}
.how__asset-notes {
  display: grid;
  gap: var(--sp-1);
  margin: var(--sp-4) 0 0;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--edge);
}
.how__asset-notes dt {
  font-family: var(--font-mono);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--quiet);
}
.how__asset-notes dd {
  margin: 0 0 var(--sp-3);
  max-width: var(--measure);
  font-size: var(--t-1-size);
  line-height: 1.6;
  color: var(--ink);
  overflow-wrap: anywhere;
}
.how__asset-notes dd:last-child { margin-bottom: 0; }

/* ---------- 5. What failed ---------- */
.how__fail {
  display: grid;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--edge);
}
@media (min-width: 900px) {
  .how__fail { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-5); align-items: start; }
}
.how__fail-fig { margin: 0; }
.how__fail-fig img {
  width: 100%;
  height: auto;
  border-radius: var(--r-2);
}
.how__fail-body h3 { margin-bottom: var(--sp-2); }
.how__fail-body .how__prompt { margin-bottom: var(--sp-3); }

/* The /five gel disclosure. Two candidate frames side by side with the
   numbers under each — no new colour, no panel: the same hairline rule
   the rest of the page uses to separate a claim from its evidence. */
.how__evidence {
  display: grid;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
}
@media (min-width: 760px) {
  .how__evidence { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-5); }
}
.how__evidence figure { margin: 0; }
.how__evidence img {
  width: 100%;
  height: auto;
  border-radius: var(--r-2);
  background: var(--pad);
}
.how__evidence figcaption {
  margin-top: var(--sp-2);
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  color: var(--quiet);
}

/* ---------- tables (discarded seeds, recon ledger, the gate) ---------- */
.how__table-wrap {
  margin-top: var(--sp-4);
  overflow-x: auto;
}
.how__table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-1-size);
  line-height: 1.55;
}
.how__table th,
.how__table td {
  padding: var(--sp-3) var(--sp-3) var(--sp-3) 0;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--edge);
  color: var(--ink);
}
.how__table thead th {
  font-family: var(--font-mono);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--quiet);
  /* the one gel rule in the table: a header underline, a graphic */
  border-bottom: 2px solid var(--accent-fill);
}
.how__table td:first-child { white-space: nowrap; padding-right: var(--sp-4); }
.how__table tbody td:last-child { min-width: 32ch; }
/* A pass/fail column reads as a status, never as a beat (§L.3).
   Scoped through .how__table on purpose: `.how__table td` above is
   (0,2,0) and a bare `.how__pass` at (0,1,0) loses to it silently —
   the same specificity trap as base.css's a[href] at (0,1,1). Caught
   by screenshot, not by reading the file: the first render printed
   both columns in --ink and looked deliberate. */
.how__table .how__pass { color: var(--ok); }
.how__table .how__fail-mark { color: var(--stop); }

/* ---------- 7. The commits ---------- */
.how__commits-status {
  margin-top: var(--sp-4);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  color: var(--quiet);
}
.how__commits-status[data-state="error"] {
  padding: var(--sp-3);
  border: 1px solid var(--stop);
  border-radius: var(--r-1);
  color: var(--stop);
}
.how__commits {
  margin: var(--sp-4) 0 0;
  border-top: 1px solid var(--edge);
}
.how__commit {
  display: grid;
  gap: var(--sp-1) var(--sp-4);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--edge);
}
@media (min-width: 760px) {
  .how__commit { grid-template-columns: 10ch 12ch minmax(0, 1fr); align-items: baseline; }
}
/* The hash is the one identifier on the page that is machine-verifiable,
   so it carries the chapter gel as TEXT (--accent: 10.40:1 dark,
   5.98:1 light). Sparing by construction — seven characters a row. */
.how__commit-hash {
  font-family: var(--font-mono);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  color: var(--accent);
  font-weight: 700;
}
.how__commit-date {
  font-family: var(--font-mono);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  color: var(--quiet);
}
.how__commit-subject {
  max-width: var(--measure);
  font-size: var(--t0-size);
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* ---------- mobile collapse (<760px), §D ---------- */
@media (max-width: 759px) {
  /* §K.3 — a shorter band below 760px so the headline, the sub and the
     page index all sit inside the first viewport at 390×844.
     §K.2 — at this width the caption cannot avoid the magenta-lit head
     of the drum, so it steps off the material entirely and sits under
     the image on the ground, where it takes the ground's own ink and
     measures 17.88:1 dark / 15.70:1 light. No scrim, no card. */
  .how__bench { height: auto; background: none; }
  .how__bench-img { height: auto; aspect-ratio: 16 / 9; }
  .how__bench-caption {
    position: static;
    max-width: none;
    text-align: left;
    padding: var(--sp-2) var(--gutter) 0;
    color: var(--ink);
  }
  .how .how__bench-caption a[href],
  .how .how__bench-caption a[href]:hover,
  .how .how__bench-caption a[href]:focus-visible {
    color: var(--ink);
    text-decoration-color: var(--accent-fill);
  }
  .how__index { margin-top: var(--sp-4); }
  .how__controls { gap: var(--sp-2); }
  .how__ring { margin-left: 0; }
  .how__track { height: 80px; }
}

/* ---------- reduced motion, §F.7 ---------- */
@media (prefers-reduced-motion: reduce) {
  /* The bench does not fade in; it is simply there. */
  .how__bench-img { opacity: 1; animation: none; }
  /* The window and playhead stop sliding; how.js steps them once per bar
     instead of every frame, so the diagram still tells the truth without
     anything moving continuously. */
  .how__window, .how__playhead { transition: none; }
  .how__asset-fig, .how__diagram { position: static; }
}

/* ============================================================
   index.css — `/` The Instrument
   Scoped under .home. Tokens only: see tokens.css. Source:
   docs/design-system-v2.md §D.1, §E, §F, §G.
   ============================================================ */

.home { display: block; }

/* ---------- 1. Hero / the fold ---------- */
.home__hero {
  position: relative;
  overflow: hidden;
  /* §D.1: nav is 64px. Subtracted here so nav + hero together fill exactly
     the first viewport — §K.3 requires the ring to sit fully inside it. */
  min-height: calc(100dvh - 64px);
  display: flex;
  align-items: flex-end;
  /* The stage under the photograph is the chapter GEL itself, not --ground.
     Two reasons, both load-bearing:
       1. --ground is theme-dependent and the photograph is not, so a
          --ground stage flashed pale-then-magenta (light) or swallowed the
          near-black headline for the whole 350ms load delay (dark).
       2. --chapter is theme-invariant and carries --chapter-on at 5.21:1 by
          construction (§L.7), so the headline is legible from the first
          painted frame and stays legible if the asset never arrives.
     This is a lantern flooding the stage before the drum resolves, which is
     what §L says the colour system IS. It is not a scrim: it sits UNDER the
     photograph, full bleed, with no edge anywhere. */
  background-color: var(--chapter);
}
.home__hero-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 30%;
  z-index: 0;
  /* §K.2(c) — a material-consistent grade applied to THE ASSET, the same
     move the amendment sanctions doing in post, done at render time because
     the .webp is shared with /how (which documents it) and must not change.
     Measured: the lit head landed at luminance 0.19–0.22, the one band where
     neither ink clears AA — too bright for a light ink (3.4:1), too dark for
     a dark one (4.2:1). Pushing the gel back up to full lantern output lifts
     it to 0.24–0.27, where --chapter-on measures 4.9–5.5:1. It reads as more
     gel, not less photograph — HSV saturation moves under a point.
     This is a filter on the image element: there is no overlay, no panel and
     no rectangle, which is the thing defect #12 was actually about. */
  filter: brightness(1.16) saturate(1.06);
  opacity: 0;
  animation: home-hero-fade var(--t-scene) linear forwards;
  animation-delay: 350ms;
}
@keyframes home-hero-fade {
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .home__hero-img { opacity: 1; animation: none; }
}

.home__hero-inner {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  width: 100%;
  /* DEFECT #13 — the copy block is placed, not floated. §K.2(b): the asset's
     lit head runs from roughly 44% to 79% of the hero's height and its dark
     hoop falls away below that. The old --sp-7 bottom padding dropped the
     CTA row (and with it the pull-apart link) off the head and onto the
     hoop, where the measured background luminance is 0.006 — the link was
     sitting at 1.11:1. --sp-8 lifts the whole block back onto the lit head,
     so every one of the four type elements shares one background and one
     ink. Nothing was re-composed: the headline lands within a line of where
     it already was. */
  padding-block: var(--sp-6) var(--sp-8);
}

/* §K.2(a) — INVERT THE INK, and invert it theme-invariantly.
   The photograph is one file served to both themes, so its ink cannot be a
   theme-dependent token. The old rule used --shell (= --ground), which
   resolved near-black on dark and near-white on light against an unchanged
   magenta plate: one of the two themes was always wrong, and in the light
   theme the h1 measured 3.45:1.
   --chapter-on is the token that means exactly "the ink that goes on this
   page's gel". It is #0B0A12 in both themes, by design (§L.4), because the
   fill it answers to does not move either. */
.home__hero-copy {
  max-width: 100%;
  color: var(--chapter-on);
}
.home__hero-copy h1 {
  color: var(--chapter-on);
  /* §K.1: hero display type must occupy the fold. 11.5vw (not the raw 11vw
     the amendment quotes) is what's needed for this exact phrase, at this
     exact font, to actually clear the required ≥60vw width at 1440 —
     verified by measurement, see the build report. Still inside the
     64/168px clamp bounds §K.1 specifies. */
  font-size: clamp(64px, 11.5vw, 168px);
  line-height: 0.92;
  letter-spacing: -0.03em;
  text-wrap: wrap;
  /* A tight shadow in the ink's own colour, not the bone glow this used to
     carry. It thickens the dark ink against the grain of the head; it is one
     pixel of blur and cannot function as a scrim. */
  text-shadow: 0 1px 2px color-mix(in srgb, var(--chapter-on) 38%, transparent);
}
.home__hero-sub {
  margin-top: var(--sp-4);
  color: var(--chapter-on);
  text-shadow: 0 1px 2px color-mix(in srgb, var(--chapter-on) 38%, transparent);
  max-width: 36ch;
}
.home__hero-ctas {
  margin-top: var(--sp-5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}
/* Two classes deliberately, and this is the whole of defect #13's mechanism:
   base.css sets `a[href] { color: var(--ink) }` at (0,1,1), which outranked
   the old single-class `.home__pull-link` at (0,1,0). The link never wore
   the colour this file gave it — it inherited --ink and painted near-black
   on the hero's darkest region in the light theme. Scoping to the CTA row
   takes it to (0,2,0) and the declaration below actually lands. */
.home__hero-ctas .home__pull-link {
  color: var(--chapter-on);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  text-underline-offset: 0.2em;
  text-decoration: underline;
  /* The underline carries the same ink as the glyphs. On the gel plate the
     --edge hairline it used to wear was invisible, which cost the link its
     only non-colour affordance. */
  text-decoration-color: var(--chapter-on);
  text-decoration-thickness: 1px;
  text-shadow: 0 1px 2px color-mix(in srgb, var(--chapter-on) 38%, transparent);
  transition: text-decoration-thickness var(--t-hover) var(--ease),
              opacity var(--t-hover) var(--ease);
}
/* Hover/focus is a weight change, not a colour change: any hue swap here
   would have to leave the measured 5:1 band the ink sits in. */
.home__hero-ctas .home__pull-link:hover,
.home__hero-ctas .home__pull-link:focus-visible {
  color: var(--chapter-on);
  text-decoration-color: var(--chapter-on);
  text-decoration-thickness: 3px;
}
/* Both buttons carry their own opaque fill, so their contrast is decided by
   tokens and not by whatever pixel they land on — .btn is --pad/--ink
   (16.61:1 dark, 14.31:1 light) and .btn--accent is --accent-fill/--chapter-on
   (5.21:1, theme-invariant). They need no help from the material. */
.home__mute[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

/* §K.3: the ring is a fixed corner mark, out of the copy's flow, so the
   headline is free to run to full frame width without collision — and
   sized/positioned so it never crosses the bottom of the first viewport. */
.home__hero-ring {
  position: absolute;
  top: var(--sp-6);
  right: var(--gutter);
  z-index: 3;
}
.home__hero-ring fiv4-ring {
  --ring-size: 30vmin;
}

@media (max-width: 759px) {
  /* The --sp-6 bottom padding this used to carry is deliberately gone. At
     390×844 the CTA row wraps to two lines, which pushed the pull-apart link
     onto the drum's dark hoop and measured it at 1.25:1 — defect #13 again,
     arriving through the mobile breakpoint. Inheriting the --sp-8 bottom
     padding lifts the wrapped row back onto the lit head, where the rest of
     the copy already sits. */
  /* Below 760px the general mobile-collapse rule (§D) puts the ring above
     the headline rather than beside it — a corner badge here would collide
     with the wider, §K.1-mandated headline block at small viewports. */
  .home__hero-ring {
    position: static;
    order: -1;
    align-self: center;
    margin-bottom: var(--sp-4);
  }
  .home__hero-ring fiv4-ring { --ring-size: 40vw; }
  .home__hero-copy { max-width: 100%; }
}

/* ---------- 2. Pull-apart (pinned) — "Beat by beat" ---------- */
.home__pullapart {
  padding-block: var(--section-gap) 0;
}
.home__pullapart-head h2 {
  margin-bottom: var(--sp-5);
}

.home__pullapart-track {
  position: relative;
}
@media (prefers-reduced-motion: no-preference) {
  .home__pullapart-track { height: 350vh; }
  .home__pullapart-stage {
    position: sticky;
    top: 0;
    height: 100vh;
    display: flex;
    align-items: center;
    overflow: hidden;
  }
}
/* Reduced motion is a design, not a kill switch (§F.7). The pinned scrub is
   gone, so nothing can "become" active — but the list still has to say which
   beat the instrument is holding. §L.6 fixes that for the ring: reduced
   motion holds the beat-1 state, magenta. The list holds the same state, so
   the two objects still agree on the page. Row one is lit and stays lit;
   rows two to five keep their --edge arcs and their own numeral gels. */
@media (prefers-reduced-motion: reduce) {
  .home__pullapart-stage {
    position: static;
    padding-block: var(--sp-6);
  }
  .home__pullapart-row[data-step="0"] .home__pullapart-arc {
    background: var(--row-gel);
    filter: drop-shadow(0 0 3px var(--row-gel));
  }
  .home__pullapart-row[data-step="0"]::before { opacity: 1; }
}

.home__pullapart-grid {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-5);
  width: 100%;
}
.home__pullapart-ring fiv4-ring {
  --ring-size: 30vmin;
}
.home__pullapart-rows {
  width: 100%;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
/* ---- the one place per-beat colour is literally the subject ----
   §L gives each of the five beats its own gel and §L.6 runs them round the
   ring. This section is the ring unrolled into a list, so row N takes gel N
   and the row and the arc beside it agree with the arc the ring is lighting
   at the same moment. Two tokens per row, never one: --row-gel is the LIT
   ARC (a fill, full chroma, identical in both themes) and --row-ink is the
   INDEX NUMERAL (type, so it must be the -text variant — the raw gels
   measure 1.10–4.97:1 as text on the light ground and every one of them
   fails, §L.7). Collapsing these two into a single custom property is the
   exact mistake tokens.css warns about for --accent / --accent-fill. */
/* One fade, used by both material layers so they cannot drift apart. It cuts
   all four edges, not just the right one: with only a horizontal falloff the
   row still showed a crisp left edge and two hairlines top and bottom — a
   rectangle you can see is a rectangle, whatever its opacity. Intersected,
   the bloom has no boundary anywhere and reads as light on the row rather
   than a plate behind it. */
.home__pullapart-row {
  --row-fade:
    linear-gradient(to right, transparent 0%, #000 7%, rgba(0,0,0,0.66) 26%, transparent 74%),
    linear-gradient(to bottom, transparent 0%, #000 34%, #000 66%, transparent 100%);
}
.home__pullapart-row { --row-gel: var(--beat-1); --row-ink: var(--beat-1-text); }
.home__pullapart-row[data-step="1"] { --row-gel: var(--beat-2); --row-ink: var(--beat-2-text); }
.home__pullapart-row[data-step="2"] { --row-gel: var(--beat-3); --row-ink: var(--beat-3-text); }
.home__pullapart-row[data-step="3"] { --row-gel: var(--beat-4); --row-ink: var(--beat-4-text); }
.home__pullapart-row[data-step="4"] { --row-gel: var(--beat-5); --row-ink: var(--beat-5-text); }

.home__pullapart-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  border-radius: var(--r-2);
  overflow: hidden;
  isolation: isolate;
}
/* The bloom is anchored to the lit arc and dissolves across the row instead
   of filling it. It used to be a full-bleed plate at 0.5, which in the light
   theme rendered as a hard-edged grey rounded rectangle behind the active
   row — the same shape as defect #12 on /five, and the reason that panel is
   being removed rather than re-tuned. Masked to a left-anchored falloff it
   has no edge to notice: the beat lights, and the light spills a little way
   along the row. Same mask on both layers so they move together. */
.home__pullapart-bloom {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
  /* Same reasoning as the active state below: material weight rides on
     --ground-opacity so the pale theme gets less of a dark frame than the
     dark theme does. This is the value the JS-off / reduced-motion static
     list renders at, where every row shows its bloom at once. */
  opacity: calc(var(--ground-opacity) * 0.45);
  -webkit-mask-image: var(--row-fade);
  -webkit-mask-composite: source-in;
  mask-image: var(--row-fade);
  mask-composite: intersect;
}
/* The gel wash sits over the photograph and carries the per-beat colour the
   art itself does not: all five beat-N.webp frames came back recoloured to
   the page's magenta chapter gel, so the frames say "this is /" and it is
   this layer that says "this is beat three". color-mix keeps it a token
   derivation — no new hex, and it stays pale over the light ground because
   the gel is only ever a minority of the mix. */
.home__pullapart-row::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(to right,
    color-mix(in srgb, var(--row-gel) 26%, transparent) 0%,
    color-mix(in srgb, var(--row-gel) 10%, transparent) 30%,
    transparent 76%);
  -webkit-mask-image: var(--row-fade);
  -webkit-mask-composite: source-in;
  mask-image: var(--row-fade);
  mask-composite: intersect;
}
.home__pullapart-index {
  position: relative;
  z-index: 1;
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  /* the beat's own gel, in its text variant — AA in both themes */
  color: var(--row-ink);
  flex: 0 0 auto;
}
.home__pullapart-arc {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  width: 28px;
  height: 8px;
  border-radius: var(--r-3);
  /* unlit arcs are --edge, exactly as the ring's are (§L.6) */
  background: var(--edge);
}
.home__pullapart-copy {
  position: relative;
  z-index: 1;
}
.home__pullapart-copy h3 {
  font-size: var(--t2-size);
  line-height: var(--t2-lh);
  letter-spacing: var(--t2-track);
  font-weight: var(--t2-weight);
  color: var(--ink);
}
.home__pullapart-copy p {
  margin-top: var(--sp-1);
  color: var(--quiet);
  font-size: var(--t-1-size);
}

@media (prefers-reduced-motion: no-preference) {
  .home__pullapart-bloom {
    opacity: 0;
    transition: opacity var(--t-move) var(--ease);
  }
  .home__pullapart-row::before {
    transition: opacity var(--t-move) var(--ease);
  }
  /* Derived from --ground-opacity rather than fixed, because that token
     exists for exactly this: material weight is theme-dependent. The frames
     are dark magenta, so on the dark stage they add light (0.20) and on the
     pale one they subtract it (0.16) — at the old flat 0.5 the light theme's
     active row pulled its caption down to 3.78:1 against --quiet while the
     dark theme still had 5.5:1 of headroom. One number could not serve both.
     The row still reads as active from four other signals: the arc lights,
     the arc slides in, the copy comes to full opacity, the gel washes. */
  .home__pullapart-row.is-active .home__pullapart-bloom {
    opacity: calc(var(--ground-opacity) * 0.6);
  }
  .home__pullapart-row.is-active::before { opacity: 1; }
  .home__pullapart-arc {
    transform: translateX(-24px);
    opacity: 0.4;
    transition: transform var(--t-move) var(--ease), opacity var(--t-move) var(--ease), background-color var(--t-move) var(--ease);
  }
  /* the arc LIGHTS in the beat's gel, with a drop-shadow in the same hue so
     it reads as light rather than paint — the same treatment §L.6 gives the
     ring's lit arc, so the two objects agree on screen. --row-gel (the fill)
     and not --row-ink: this is a lit surface, not type. */
  .home__pullapart-row.is-active .home__pullapart-arc {
    transform: translateX(0);
    opacity: 1;
    background: var(--row-gel);
    filter: drop-shadow(0 0 3px var(--row-gel));
  }
  .home__pullapart-copy {
    opacity: 0.5;
    transition: opacity var(--t-move) var(--ease);
  }
  .home__pullapart-row.is-active .home__pullapart-copy { opacity: 1; }
}

@media (min-width: 760px) and (prefers-reduced-motion: no-preference) {
  .home__pullapart-grid {
    flex-direction: row;
    align-items: center;
    gap: var(--sp-7);
  }
  .home__pullapart-ring { flex: 0 0 auto; }
  .home__pullapart-rows { flex: 1 1 auto; max-width: none; }
}

/* ---------- 3. Accent controls — "Where the accent goes" ---------- */
.home__accent {
  padding-block: var(--section-gap);
}
.home__accent-grid {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.home__accent-copy p {
  margin-top: var(--sp-2);
  color: var(--quiet);
}
.home__accent-controls {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.home__pads {
  display: flex;
  gap: var(--sp-2);
}
.home__pad {
  width: 56px;
  height: 56px;
  border-radius: var(--r-3);
  border: 1px solid var(--edge);
  background: var(--pad);
  color: var(--quiet);
  font-family: var(--font-mono);
  font-size: var(--t1-size);
  font-variant-numeric: tabular-nums;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--t-hover) var(--ease), color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease), transform var(--t-hover) var(--ease);
}
/* --accent is the chapter gel as TEXT: correct for a hover ink and a
   1px border, both of which sit ON --pad and must clear AA there. */
.home__pad:hover, .home__pad:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}
.home__pad:active { transform: scale(0.96); }
/* ...and --accent-fill is the same gel as a FILL, carrying --chapter-on.
   Swapping either for the other is the swap tokens.css forbids: --accent as
   a fill would go dark-crimson in the light theme, and --chapter-on as a
   hover ink would be near-invisible on --pad in the dark one. */
.home__pad[aria-pressed="true"] {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}
.home__grouping {
  display: flex;
  gap: var(--sp-2);
}
.home__group-btn {
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-3);
  border: 1px solid var(--edge);
  background: var(--pad);
  color: var(--quiet);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  text-transform: uppercase;
  transition: background var(--t-hover) var(--ease), color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.home__group-btn:hover, .home__group-btn:focus-visible {
  border-color: var(--accent-2);
  color: var(--accent-2);
}
/* The grouping toggle is the standing second voice (cyan, §L). Pressed, it
   is a FILL, so it takes the raw gel --beat-2 and the ink that answers to it,
   --beat-2-on — both theme-invariant, 10.40:1. It must NOT take --accent-2
   (= --beat-2-text) as a fill: that token is darkened for the light theme
   and would carry --beat-2-on at 1.7:1. Unpressed, the hover ink and border
   correctly stay on --accent-2, because there it is type on --pad. */
.home__group-btn[aria-pressed="true"] {
  background: var(--beat-2);
  border-color: var(--beat-2);
  color: var(--beat-2-on);
}

@media (min-width: 760px) {
  .home__accent-grid {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
  }
  .home__accent-copy { max-width: 32ch; }
}

/* ---------- 4. Persistence — "Take it with you" ---------- */
.home__persistence {
  padding-block: 0 var(--section-gap);
}
.home__persistence-inner p {
  margin-top: var(--sp-2);
  color: var(--quiet);
}
.home__link-readout {
  margin-top: var(--sp-4);
  padding: var(--sp-3);
  border-radius: var(--r-1);
  border: 1px solid var(--edge);
  color: var(--quiet);
  font-size: var(--t-1-size);
  word-break: break-all;
  max-width: var(--measure);
}

/* ============================================================
   kit.css — `/kit` The kit
   Scoped under .kit. Tokens only: see tokens.css.
   Source: docs/design-system-v2.md §C, §D, §D.4, §E, §F, §H, §K,
   and §L (colour system v3 — five gels).

   COLOUR: repainted against §L. /kit is chapter beat 4, vermilion
   #FF3B1F — tokens.css sets --chapter/--chapter-text/--chapter-on
   from body[data-page="kit"]. Every walnut-era alias this file used
   is gone, and there are no literal hexes anywhere:

     --shell     -> --ground      as a surface
     --shell     -> --beat-5-on   as the theme-invariant LIGHT ink
                                  over theme-invariant dark material
     --head      -> --ink         primary voice
     --chrome    -> --quiet       secondary voice
     --ink-quiet -> --quiet
     --felt      -> --edge        hairlines, borders
     --brass     -> --accent      where it is TEXT on --ground/--pad
     --brass     -> --accent-fill where it is a FILL
     --patina    -> a per-beat gel

   Per §L.4 a fill is never var(--accent) and text is never
   var(--accent-fill).

   §L.6 — THE FIVE VOICES ARE THE FIVE BEATS, and on this page that
   is not a metaphor: the recoloured objects came back gelled one
   per voice (kick magenta, rim cyan, hat lime, ping vermilion, sub
   violet), which is beat order. So every voice-scoped element takes
   its own gel rather than the flat chapter colour, and the page runs
   magenta -> cyan -> lime -> vermilion -> violet exactly like the
   ring. Three tokens per voice, because a gel needs three (§L.4):

     --voice-gel    the gel itself — lit graphics on THEME-INVARIANT
                    material (the dark scope plate). Never type on
                    --ground.
     --voice-ink    the gel as TEXT / as a control on --ground or
                    --pad. Theme-dependent, AA in both themes.
     --voice-on     the ink that goes ON a --voice-gel fill.

   --voice-trace is --voice-gel for four of the five. Violet is the
   exception §L.4 already names: raw #7B3FE4 is 3.44:1 on a dark
   ground, which is why --beat-5-text exists — but --beat-5-text is
   theme-dependent and the scope plate is a photograph that is dark
   in BOTH themes, so it cannot be used here. The lift is therefore
   made along the system's own axis: mix the gel toward the ink the
   system already says belongs on violet (--beat-5-on). It is a
   token derivation, not a new colour, and it is theme-invariant
   because both of its operands are.
   ============================================================ */

.kit { display: block; }

/* ---------- the five voices, one gel each (§L.6) ---------- */
.kit [data-voice="kick"], .kit [data-scope="kick"] {
  --voice-gel: var(--beat-1); --voice-ink: var(--beat-1-text); --voice-on: var(--beat-1-on);
  --voice-trace: var(--beat-1);
}
.kit [data-voice="rim"], .kit [data-scope="rim"] {
  --voice-gel: var(--beat-2); --voice-ink: var(--beat-2-text); --voice-on: var(--beat-2-on);
  --voice-trace: var(--beat-2);
}
.kit [data-voice="hat"], .kit [data-scope="hat"] {
  --voice-gel: var(--beat-3); --voice-ink: var(--beat-3-text); --voice-on: var(--beat-3-on);
  --voice-trace: var(--beat-3);
}
.kit [data-voice="ping"], .kit [data-scope="ping"] {
  --voice-gel: var(--beat-4); --voice-ink: var(--beat-4-text); --voice-on: var(--beat-4-on);
  --voice-trace: var(--beat-4);
}
.kit [data-voice="sub"], .kit [data-scope="sub"] {
  --voice-gel: var(--beat-5); --voice-ink: var(--beat-5-text); --voice-on: var(--beat-5-on);
  --voice-trace: color-mix(in srgb, var(--beat-5) 62%, var(--beat-5-on));
}

/* ============================================================
   2. Hero / the fold — §D.4, §K.1–K.4, §L
   One asset holds the whole fold: the kit on a black stage under
   the chapter lantern. §K.4 lever (d) — a real asset occupying the
   fold is the only lever that reaches stddev ≥ 18 on BOTH themes
   without adding chroma (a tiled ground in `multiply` at 22% is
   arithmetically a no-op on dark; that was the /five failure). The
   fold is identical in both themes because the lantern is: a gel is
   a physical filter and the house lights coming up does not change
   what colour it is (§L.2).
   ============================================================ */
.kit__hero {
  position: relative;
  overflow: hidden;
  /* nav is 64px (§K.3) — nav + hero fill exactly the first viewport,
     so nothing important is pushed past the fold at 1440×900. */
  min-height: calc(100dvh - 64px);
  display: flex;
  align-items: flex-end;
  /* The stage under the photograph is the chapter GEL, not --ground:
     --ground is theme-dependent and the photograph is not, so a
     --ground stage would flash pale-then-vermilion in the light theme
     and swallow the light ink in the dark one for the whole 350ms
     load delay. --chapter is theme-invariant. It sits UNDER the
     photograph, full bleed, with no edge anywhere — a lantern
     flooding the stage before the kit resolves, not a scrim. */
  background-color: var(--chapter);
  isolation: isolate;
}

.kit__hero-img {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* the lantern blooms behind the shells in the upper third and falls
     off to near-black across the bottom; hold that bottom band, which
     is where §K.2 lever (b) puts the type */
  object-position: 50% 38%;
  /* §F.5 — material fades in at 350ms over 900ms, linear. */
  opacity: 0;
  animation: kit-material-fade var(--t-scene) linear forwards;
  animation-delay: 350ms;
}
@keyframes kit-material-fade { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .kit__hero-img { opacity: 1; animation: none; }
}

/* §B.3 grain at §K.4 lever (c) amplitude. Ink hairlines in `overlay`:
   a texture with no edge and no shape — not a tint and not a panel. */
.kit__hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image:
    repeating-linear-gradient(0deg, var(--ink) 0px, transparent 1px, transparent 3px),
    repeating-linear-gradient(90deg, var(--ink) 0px, transparent 1px, transparent 3px);
}

.kit__hero-inner {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-block: var(--sp-6) var(--sp-5);
  /* §K.2 lever (a) — INVERT THE INK, and invert it theme-invariantly.
     The photograph is one file served to both themes, so its ink
     cannot be a theme-dependent token; --head (= --ink) used to sit
     here and flipped to near-black in the light theme over an
     unchanged near-black stage. --beat-5-on is the system's ink for
     exactly this case — the LIGHT ink, for a surface too dark to
     carry a dark one — and it is declared once in the bare :root and
     never redefined by a theme block, so it is theme-invariant by
     construction. Same token, same reason, as /five's hero. */
  color: var(--beat-5-on);
}

/* §K.1 — hero display type occupies the fold. The amendment's binding
   number is the OUTCOME: the h1 must span ≥60% of viewport width at
   1440. "Five voices." is 12 characters against `/`'s 15, and at the
   amendment's 11.5vw / 168px ceiling it measures 54.7% — short. The
   coefficient and ceiling are raised to 13vw / 190px, the smallest
   pair that clears the requirement for this exact phrase at this
   exact face. line-height and tracking are unchanged from §K.1. */
.kit__hero h1 {
  color: var(--beat-5-on);
  font-size: clamp(64px, 13vw, 190px);
  line-height: 0.92;
  letter-spacing: -0.03em;
  text-wrap: nowrap;
}
/* §K.2 — no card, no scrim. Light ink over the lantern's own falloff;
   measured against the real pixels behind it, see the build report. */
.kit__hero-sub {
  margin-top: var(--sp-4);
  max-width: 42ch;
  color: var(--beat-5-on);
}
.kit__hero-ctas {
  margin-top: var(--sp-5);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}
/* Both buttons carry their own opaque fill, so their contrast is
   decided by tokens and not by whichever pixel of the stage they land
   on — .btn is --pad/--ink and .btn--accent is --accent-fill with
   --chapter-on (5.53:1, theme-invariant). The BPM readout takes the
   same opaque chip for the same reason: it used to be --chrome on a
   bare border and was being read against a photograph. */
.kit__bpm {
  margin: 0;
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-1);
  border: 1px solid var(--edge);
  background: var(--pad);
  color: var(--ink);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
}
.kit__hero [data-mute][aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

@media (max-width: 759px) {
  /* At 390×844 `cover` crops hard to the middle of the frame, which is
     the brightest part of the bloom; the bottom band there measures
     0.14 rather than 0.01. A grade on THE IMAGE ELEMENT pulls it back
     a stop — permitted by §K.2(c), and it is a filter on the asset,
     not an overlay: no rectangle, no edge, nothing composited over the
     type. Nothing is clipped at this size (§K.3): the headline drops
     to the 64px clamp floor and wraps. */
  .kit__hero-img { filter: brightness(0.88) saturate(1.04); }
  .kit__hero h1 { text-wrap: balance; }
  .kit__hero-sub { max-width: 100%; }
  .kit__hero-inner { padding-block: var(--sp-5) var(--sp-5); }
}

/* ============================================================
   The rail — five voices, five gels, in beat order (§L.6)
   ============================================================ */
.kit__rail {
  padding-block: var(--sp-5);
  border-bottom: 1px solid var(--edge);
}
.kit__rail-list {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--sp-3);
  margin: 0;
}
/* TWO CLASSES, deliberately: base.css sets `a[href] { color: var(--ink) }`
   at (0,1,1) and would silently outrank a single-class rule here — the
   same specificity trap that cost `/` its pull-apart link. (0,2,0) lands.
   The glyphs stay --ink (17.88:1 / 15.70:1) and the voice's gel is
   carried by the index numeral and the lit arc above it, so the row is
   never distinguished by colour alone. */
.kit__rail-list .kit__rail-link {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  padding-block: var(--sp-2);
  color: var(--ink);
  text-decoration: none;
  transition: color var(--t-hover) var(--ease);
}
.kit__rail-list .kit__rail-link:hover,
.kit__rail-list .kit__rail-link:focus-visible {
  color: var(--voice-ink);
}
/* the lit arc: a graphic, full chroma, identical in both themes —
   the same treatment §L.6 gives the ring's lit arc */
.kit__rail-arc {
  display: block;
  width: 100%;
  height: 4px;
  border-radius: var(--r-3);
  background: var(--voice-gel);
  transition: height var(--t-hover) var(--ease), filter var(--t-hover) var(--ease);
}
.kit__rail-list .kit__rail-link:hover .kit__rail-arc,
.kit__rail-list .kit__rail-link:focus-visible .kit__rail-arc {
  height: 8px;
  filter: drop-shadow(0 0 3px var(--voice-gel));
}
/* type, so the -text variant — the raw gels are 1.10–4.97:1 as type on
   the light ground and every one of them fails (§L.7) */
.kit__rail-index {
  color: var(--voice-ink);
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
}
.kit__rail-name {
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  text-transform: uppercase;
}
@media (max-width: 759px) {
  .kit__rail-list { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--sp-2); }
  .kit__rail-name { font-size: var(--t-2-size); }
}

/* ============================================================
   3–7. The five voice blocks — §D.4
   ============================================================ */
.kit__lede {
  color: var(--quiet);
  max-width: var(--measure);
}

.kit__voices {
  padding-block: var(--section-gap);
  display: flex;
  flex-direction: column;
  gap: var(--section-gap);
}

.kit__voice {
  display: grid;
  gap: var(--sp-5);
  align-items: start;
}

.kit__object {
  margin: 0;
  border-radius: var(--r-2);
  overflow: hidden;
  background-color: var(--pad);
}
.kit__object img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  aspect-ratio: 3 / 4;
}

/* the voice's own gel, as type, on --ground — so the -text variant */
.kit__index {
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  color: var(--voice-ink);
}
.kit__voice h2 {
  margin-top: var(--sp-2);
  color: var(--ink);
}
.kit__desc {
  margin-top: var(--sp-3);
  color: var(--quiet);
  max-width: var(--measure);
}

/* ---------- the scope: canvas over the smoked-glass plate ----------
   The plate is a photograph of dark glass in a metal bezel and it does
   not change with the theme, so nothing painted on it may use a
   theme-dependent token. Everything here is theme-invariant: the trace
   is the voice's own gel and the type is --beat-5-on, the system's
   light ink. --quiet used to sit on the state line and measured 1.6:1
   against the glass in the light theme. */
.kit__scope {
  position: relative;
  margin-top: var(--sp-5);
  border-radius: var(--r-2);
  overflow: hidden;
  aspect-ratio: 1400 / 788;
  background-color: var(--beat-1-on);
  transition: box-shadow var(--t-move) var(--ease);
}
.kit__scope-plate {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
/* the canvas sits inside the plate's bezel — the glass, not the chrome.
   `color` and `--scope-ink` are the handoff to kit.js: the stylesheet
   decides every colour the scope draws and the script only reads them,
   so there is still not one colour value in the JS. */
.kit__scope-canvas {
  position: absolute;
  left: 9%;
  top: 13%;
  width: 82%;
  height: 58%;
  z-index: 1;
  color: var(--voice-trace);
  --scope-ink: var(--beat-5-on);
}
.kit__scope-state {
  position: absolute;
  left: 9%;
  right: 9%;
  bottom: 8%;
  z-index: 2;
  margin: 0;
  max-width: none;
  color: var(--beat-5-on);
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  line-height: 1.3;
}
.kit__scope[data-live] .kit__scope-state { color: var(--voice-gel); }
.kit__scope.is-struck { box-shadow: inset 0 0 0 2px var(--voice-gel); }

/* ---------- the spec table — real, in-use synthesis values ---------- */
.kit__spec {
  margin: var(--sp-4) 0 0;
  display: grid;
  gap: var(--sp-1);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
}
.kit__spec-row {
  display: flex;
  gap: var(--sp-3);
  align-items: baseline;
  padding-block: var(--sp-1);
  border-bottom: 1px solid var(--edge);
}
.kit__spec dt {
  flex: 0 0 9.5em;
  color: var(--quiet);
  font-weight: var(--t-2-weight);
}
.kit__spec dd {
  margin: 0;
  color: var(--ink);
}

/* ---------- controls ---------- */
.kit__controls {
  margin-top: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  align-items: flex-start;
}
.kit__control {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  max-width: 460px;
}
.kit__control label {
  flex: 0 0 5.5em;
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  color: var(--quiet);
}
.kit__control output {
  flex: 0 0 7em;
  text-align: right;
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  color: var(--ink);
}
.kit__control input[type="range"] {
  flex: 1 1 auto;
  min-width: 0;
  appearance: none;
  -webkit-appearance: none;
  height: 24px;
  background: transparent;
  cursor: pointer;
}
.kit__control input[type="range"]::-webkit-slider-runnable-track {
  height: 2px;
  border-radius: var(--r-1);
  background: var(--edge);
}
.kit__control input[type="range"]::-moz-range-track {
  height: 2px;
  border-radius: var(--r-1);
  background: var(--edge);
}
/* The thumb is a small control read against --ground in both themes, so
   it takes --voice-ink (the -text variant) and NOT the raw gel: lime on
   the pale ground is 1.16:1 and the thumb would disappear in the light
   theme. This is the same rule as type, for the same reason. */
.kit__control input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin-top: -8px;
  border-radius: var(--r-3);
  background: var(--voice-ink);
  border: 1px solid var(--voice-ink);
  transition: transform var(--t-hover) var(--ease);
}
.kit__control input[type="range"]::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: var(--r-3);
  background: var(--voice-ink);
  border: 1px solid var(--voice-ink);
  transition: transform var(--t-hover) var(--ease);
}
.kit__control input[type="range"]:hover::-webkit-slider-thumb { transform: scale(1.12); }
.kit__control input[type="range"]:hover::-moz-range-thumb { transform: scale(1.12); }
.kit__control input[type="range"]:focus-visible {
  outline: 2px solid var(--voice-ink);
  outline-offset: 3px;
}

.kit__strike { align-self: flex-start; }
.kit__strike:hover,
.kit__strike:focus-visible {
  border-color: var(--voice-ink);
  color: var(--voice-ink);
}

.kit__note {
  margin: 0;
  padding-top: var(--sp-4);
  border-top: 1px solid var(--edge);
  color: var(--quiet);
  max-width: var(--measure);
}

/* alternating sides — §D.4: voices 1, 3, 5 image right; 2, 4 image left */
@media (min-width: 760px) {
  .kit__voice {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.72fr);
    gap: var(--sp-6);
  }
  .kit__voice .kit__object { grid-column: 2; grid-row: 1; }
  .kit__voice .kit__panel { grid-column: 1; grid-row: 1; }
  /* the panel always gets the 1fr track — the track sizes swap with the
     columns, or the even-numbered scopes would be narrower than the odd */
  .kit__voice:nth-of-type(even) {
    grid-template-columns: minmax(0, 0.72fr) minmax(0, 1fr);
  }
  .kit__voice:nth-of-type(even) .kit__object { grid-column: 1; }
  .kit__voice:nth-of-type(even) .kit__panel { grid-column: 2; }
  .kit__object { position: sticky; top: var(--sp-5); }
}
@media (max-width: 759px) {
  /* mobile collapse rule (§D): visual then control, always */
  .kit__voice { grid-template-columns: 1fr; }
  .kit__object img { aspect-ratio: 4 / 3; }
  .kit__spec dt { flex: 0 0 8em; }
  .kit__control { flex-wrap: wrap; }
  .kit__control input[type="range"] { flex: 1 1 120px; }
}

/* ============================================================
   8. All five at once — five scopes stacked over one bar. §D.4
   The bar IS the five beats, so it is the second place §L.6 applies
   literally: cell N lights in --beat-N and carries --beat-N-on.
   ============================================================ */
.kit__all {
  padding-block: 0 var(--section-gap);
}
.kit__all-sub {
  margin-top: var(--sp-3);
  color: var(--quiet);
}
.kit__all-stack {
  margin-top: var(--sp-5);
  padding: var(--sp-4);
  border: 1px solid var(--edge);
  border-radius: var(--r-2);
  background-color: var(--pad);
}

.kit__bar {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--sp-1);
  margin: 0 0 var(--sp-3);
  padding-left: calc(7.5em + var(--sp-3));
}
.kit__bar li {
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-1);
  border: 1px solid var(--edge);
  color: var(--quiet);
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
  /* DEFECT #16 — THE FILL AND ITS INK ARE ONE PAIRING AND MUST MOVE
     TOGETHER, SO THEY SNAP.
     This rule used to cross-fade background-color and color over the
     same 380ms. Sampled at the SAME INSTANT (alpha composited over
     --pad, which is what a partially-faded background-color actually
     is), worst instantaneous contrast measured 1.03:1 dark / 1.06:1
     light, with 6.3% / 2.7% of sampled instants under 4.5:1. The two
     steady states are correct — at transition-duration 0s the same
     sampler returns 5.19:1 worst and zero failures — so the defect is
     the crossfade itself, not the palette.

     It cannot be fixed by picking a better ink. --beat-N-on is defined
     (§L.4, §L.7) as the ink for a FULL --beat-N fill; at 50% the cell
     is not a gel, it is a half-gel over --pad, and no single ink spans
     both ends — beats 1–4 carry dark ink over a bright gel and the
     off-state carries light-ish --quiet over a dark --pad, so any
     value legible at one end is illegible at the other. §L.4's rule
     that a page must never hard-code an ink over a gel fill is a rule
     about every instant, not just the two endpoints.

     So background-color and color both change instantly, in step, and
     only the two legal pairings ever exist on screen. This is the same
     call loop.css already makes for the stage gel: a lighting desk
     snaps, and a crossfade is what a video encoder does.

     §F.7.5 ("transitions are shortened, not removed — losing colour
     transitions entirely makes state changes harder to follow") still
     holds, and is the reason border-color is still transitioned: the
     border is not type, so it cannot fail a contrast ratio at any
     alpha, and letting it decay from the gel back to --edge over
     --t-move leaves the struck cell legible for a beat after the fill
     drops. The state change gained a tail; it did not lose one.

     Declared as the shorthand deliberately. base.css's reduced-motion
     block re-declares transition-property on `*` to include
     background-color and color at var(--t-move) — the exact defect,
     reintroduced for reduced-motion visitors only. `.kit__bar li` is
     specificity 0,1,1 against the universal selector's 0,0,0, so this
     resets transition-property in both directions and the crossfade
     cannot come back. */
  transition: border-color var(--t-move) var(--ease);
}
/* a lit cell is a FILL carrying a numeral, so it needs BOTH tokens: the
   raw gel and the ink that answers to it. Beats 1–4 take the dark ink
   and beat 5 the light one — there is no single value that works for
   all five (§L.7), which is what --beat-N-on exists to say. */
.kit__bar li[data-beat="0"].is-on { background-color: var(--beat-1); border-color: var(--beat-1); color: var(--beat-1-on); }
.kit__bar li[data-beat="1"].is-on { background-color: var(--beat-2); border-color: var(--beat-2); color: var(--beat-2-on); }
.kit__bar li[data-beat="2"].is-on { background-color: var(--beat-3); border-color: var(--beat-3); color: var(--beat-3-on); }
.kit__bar li[data-beat="3"].is-on { background-color: var(--beat-4); border-color: var(--beat-4); color: var(--beat-4-on); }
.kit__bar li[data-beat="4"].is-on { background-color: var(--beat-5); border-color: var(--beat-5); color: var(--beat-5-on); }

.kit__all-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.kit__all-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.kit__all-name {
  flex: 0 0 7.5em;
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  color: var(--ink);
}
.kit__all-index { color: var(--voice-ink); margin-right: var(--sp-1); }
/* These canvases sit on --pad, which DOES move with the theme, so their
   trace is --voice-ink and not --voice-gel — the opposite call from the
   scope plate above, for exactly the reason §L.4 gives. No dark bed is
   faked under them: the panel is the panel. */
.kit__all-canvas {
  flex: 1 1 auto;
  min-width: 0;
  height: 56px;
  border-radius: var(--r-1);
  border: 1px solid var(--edge);
  background-color: transparent;
  color: var(--voice-ink);
  --scope-ink: var(--ink);
}
.kit__all-state {
  margin: var(--sp-4) 0 0;
  max-width: none;
  color: var(--quiet);
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  line-height: 1.45;
}
.kit__all[data-live] .kit__all-state { color: var(--accent); }

@media (max-width: 759px) {
  .kit__bar { padding-left: 0; }
  .kit__all-row { flex-wrap: wrap; }
  .kit__all-name { flex: 0 0 100%; }
  .kit__all-canvas { flex: 1 1 100%; height: 48px; }
}

/* ============================================================
   loop.css — `/loop` Loop room
   Scoped entirely under .loop (plus body[data-page="loop"] for
   the three page-shell rules a non-scrolling stage needs).
   Tokens only: see tokens.css. No new colours, one easing.
   Source: docs/design-system-v2.md §D.5, §E, §F.6, §F.7, §G,
   §H, the §K amendments K.1–K.4, and §L (colour system v3).

   ------------------------------------------------------------
   §L REPAINT — where the walnut-era aliases went, and why.
   Chapter gel: violet, beat 5 (§L.5). Assigned in tokens.css on
   body[data-page="loop"]; this file never names a hex.

     --shell  → --chapter-on   where it was ink ON a chapter fill
                --ground       where it was a self-grounded panel
     --head   → --chapter-on   for every element sitting on the film
                --ink          for chrome that carries its own --pad
     --brass  → --accent-fill  where it was a FILL (pressed chips,
                               struck pads). --brass aliases --accent,
                               which is TEXT — using it as a fill is
                               the exact swap §L.4 forbids.
     --felt / --patina / --chrome — this page never used them.

   THE ONE RULE THIS PAGE HAD TO DERIVE: no coloured type anywhere
   on the film. /loop's gel is violet, and violet is the one gel
   that cannot be its own text colour (3.44:1) — but the lightened
   --beat-5-text only exists in the dark theme. In the LIGHT theme
   --beat-5-text is #5B21B6, a dark violet built to be read on pale
   grey, and the film is a dark stage in BOTH themes. Dark violet
   on a dark stage is unreadable, so violet type over the film is
   impossible in one theme and therefore banned in both.

   What carries type on the film instead is --chapter-on. That is
   not a workaround, it is the token's actual job: --beat-5-on is
   "the ink placed on a violet fill", it is #F2F4F8 in BOTH themes
   because a gel is theme-invariant (§L.4), and the film IS the
   violet gel — 10 seconds of a stage lit by the lanterns this page
   is named after. The chapter's own ink is exactly the right ink
   for the chapter's own light.
   ------------------------------------------------------------ */

/* ---------- page shell: the stage does not scroll (§D.5) ----------
   The build wraps every page in nav + main + footer, so the shell
   rules have to reach those two partials. They are gated on
   body[data-page="loop"] and therefore cannot leak to any other
   page. §H gives /loop three sections — chrome, stage, key row —
   and no footer; the nav carries every route. */
html:has(body[data-page="loop"]) {
  height: 100%;
  overflow: hidden;
}
body[data-page="loop"] {
  position: relative;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
}
body[data-page="loop"] .site-footer { display: none; }

/* The nav floats over the stage so the material runs edge to edge
   behind it. Still exactly 64px tall (§K.3). */
body[data-page="loop"] .site-nav {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  z-index: 30;
  border-bottom: 0;
  transition: opacity var(--t-scene) var(--ease);
}
body[data-page="loop"].is-idle .site-nav {
  opacity: 0;
  pointer-events: none;
}
/* §K.2: the nav sits directly on the photograph here, and the plate
   runs from near-black to a bone tungsten wash across its width, so
   no single ink clears 4.5:1 along it. Fixed with the page's own
   material language rather than a scrim: every nav control becomes
   the same hardware chip the stage's other controls already are, and
   carries its own ground. The "you are here" mark stays the brass
   rule underneath — a graphic, not type. */
body[data-page="loop"] .site-nav__home,
body[data-page="loop"] .site-nav__toggle,
body[data-page="loop"] .site-nav__links a {
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-3);
  background: var(--pad);
  color: var(--ink);
}
body[data-page="loop"] .site-nav__links a[aria-current="page"] { color: var(--ink); }
body[data-page="loop"] .site-nav__links a:hover,
body[data-page="loop"] .site-nav__links a:focus-visible { color: var(--accent); }

/* ---------- the stage ---------- */
.loop {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--ground);
  display: flex;
  flex-direction: column;
  padding: calc(64px + var(--sp-4)) var(--gutter) var(--sp-4);

  /* Exposure on the film. §K.4 lever (a) is "pick the blend mode per
     theme"; for a photographed stage the honest equivalent is
     exposure — one global stop on the whole plate, the way a print
     is made. It has no onset, no edge and no gradient, so it is not
     a scrim and it cannot produce a visible seam. K.2 bans a scrim;
     it explicitly permits printing the plate down (lever c). The
     film's own beams peak at luminance 1.00 and its brightest band
     runs to 0.106 mean across the gel cycle, which is where white
     ink starts to go soft.

     0.88 is not a taste value, it is the solution to a two-sided
     constraint measured on rendered frames at 1440x900, sampled
     across the whole gel cycle rather than at one moment:
       print DOWN  →  the title's contrast over the film rises
                      (worst frame t=4.8s, a violet beam sweeping
                      the upper left: 5.31:1 at 0.88)
       print UP    →  the §K.4 ground-band stddev rises
                      (worst frame t=9.6s, a flat magenta wash with
                      no beam structure left in it: the band scores
                      18.9 at 0.88, 18.5 at 0.86, 17.7 at 0.82)
     Below ~0.84 the band drops under K.4's floor of 18; above ~0.92
     the title drops under 5:1. 0.88 is where both hold, at the
     film's WORST frame in each direction rather than its average. No chroma is added: saturate() only ever removes,
     and here it does not even do that. */
  --film-exposure: 0.88;
  --film-saturation: 1;

  /* live values written by loop.js; all default to "at rest" so the
     page composes correctly with JS off. */
  --plate-x: 0px; --plate-y: 0px;
  --lift: 0;
  --ripple-scale: 0.18;
  --ripple-opacity: 0;
  --ripple-y: 46%;
  /* The ground answers in the colour of the beat that struck it
     (§L.6 applied to the floor). Defaults to the chapter gel so the
     page composes with JS off. */
  --ripple-gel: var(--chapter);

  /* The stage wash. Both are written by loop.js on every beat — the
     hue from the lagged beat's gel, the opacity from that same beat's
     ACCENT, so the room snaps hard/soft/soft/hard/soft in 3+2 and
     hard/soft/hard/soft/soft in 2+3. loop.js reads the accents from
     the live transport, so the grouping the room is counting is
     always the grouping the instrument is playing.

     The declared values here are the state with no JavaScript running
     at all — and they are deliberately the ACCENTED intensity, not the
     unaccented one. Accent modulation is a property of a sequence, not
     of a still: with nothing snapping there is no hard and soft to
     tell apart, so the frozen room is simply the chapter gel at full
     strength. This is what the reduced-motion path and the JS-off path
     both hold, and 0.32 being the maximum the wash ever reaches is why
     every measured worst case in the build report is a true worst
     case. See .loop__gel. */
  --gel: var(--chapter);
  --gel-opacity: 0.32;
}
@media (min-width: 760px) {
  .loop { --gutter: 40px; }
}

/* THE FILM IS THEME-INVARIANT, AND THAT IS THE POINT.
   The old two-plate stage carried a per-theme exposure (--far-exposure
   went 0.92 -> 2.15) because it was standing in for a room. The film
   is not standing in for anything: it is the violet lantern, running.
   §L.2 is explicit that "the gel hex itself never changes between
   themes — a gel is a physical filter in a lantern; the house lights
   coming up does not change what colour it is", and --beat-N is
   declared exactly once for that reason. The film obeys the same
   rule, so there is no light-theme block here at all.

   That is not the light theme going missing. Everything on this page
   that is a SURFACE rather than a light flips completely: the nav
   chips, the tempo readout, the grouping and pattern chips, the five
   pads and every kbd carry --pad and --ink and go from #151322/#F2F4F8
   to #E1E5EE/#14161E. What stays put is the stage and the ink on it,
   because --chapter-on is theme-invariant too. House lights up, same
   gel, same stage, different hardware — which is what §L.3's "a lit
   stage, not a paper one" actually describes.

   It also removes a whole class of bug: with one exposure, every
   contrast and stddev figure in the build report is measured once and
   is true in both themes by construction, not by two lucky tunings. */

/* ---------- SECTION 2 — the scene (the page's one bleed) ---------- */
.loop__scene {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--ground);
  pointer-events: none;
}

/* ---------- the plate: one poster, one film, same box ----------
   The <img> and the <video> occupy an identical box and are framed
   by identical rules, so the moment the film starts there is no
   jump — the first video frame lands exactly on top of the poster
   frame it was cut from. The video is the only thing that fades. */
.loop__plate {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;

  /* FRAMING — the one thing that stops this reading as stock.
     The take is a locked-off wide of a kit dead-centre with a row
     of six lanterns across the top: the most generic possible
     staging, and the lanterns are the tell. Printing it at 1.26
     about a point below centre pushes that lantern row off the top
     edge entirely, drops the kit into the lower third where the key
     row and the Play control already live, and leaves the ring
     sitting in the beam-and-haze field the lanterns are throwing —
     which is the only part of this footage that behaves like light
     rather than like a photograph of equipment. It is a crop, not a
     fix: the frame we keep is the half of the shot that is lit air. */
  transform-origin: 50% 38%;
  transform: scale(1.26) translate3d(var(--plate-x), var(--plate-y), 0);
  filter: brightness(var(--film-exposure)) saturate(var(--film-saturation));
  will-change: transform;
}

/* The poster is unconditional and never fades: it is the composed
   page. Everything measured in the build report is measured on it. */
.loop__plate--film {
  opacity: 0;
  transition: opacity var(--t-scene) var(--ease);
}
/* Set by loop.js on the video's own `playing` event — so the film
   only ever replaces the poster once it is genuinely running, and a
   codec failure, a blocked autoplay or a slow network simply leaves
   the poster in place with nothing to notice. */
.loop__plate--film.is-live { opacity: 1; }

/* ---------- THE GEL — the stage light comes off the clock ----------
   §L.1 says "the ring lights in the colour of the beat currently
   playing, so the ring cycles through all five hues across one bar".
   Until now the film underneath it cycled too — on its own schedule,
   crossfading, landing on nothing. Two colour cycles running out of
   phase, one of which was not in five at all. This layer replaces the
   footage's cycle with the transport's: loop.js writes --gel on every
   beat event and there is deliberately NO transition on this rule, so
   the change is a hard step. A lighting desk snaps; a crossfade is
   what a video encoder does. Snapping is the truer behaviour AND the
   only one that can land on a beat.

   BLEND MODE IS THE LEGIBILITY DECISION, and it is why this is a gel
   rather than a scrim. `color` takes hue and saturation from this
   layer and LUMINOSITY from the film underneath — which is precisely
   what a physical gel does to a lit subject, and it means the wash
   cannot darken or brighten anything. Every §K.2 contrast figure and
   every §K.4 stddev figure measured before the gel existed survives
   it, because the luminance channel is untouched by construction.
   `screen` and `multiply` would both have moved luminance and forced
   the whole exposure derivation to be redone at five hues.

   --gel-opacity is therefore a SATURATION control, not a darkness
   one. It was set by eye against the five states side by side and
   then measured. 0.62 was tried first and rejected: at that strength
   the beat-3 wash turns the whole room olive-yellow, which is a flat
   recolour rather than a lighting change and sits far too close to
   the warm-neutral quadrant §L exists to keep this site out of.
   0.40 was better but still failed a check worth running: lime is
   the one gel 180 degrees from this footage's own magenta/violet, so
   `color`-blending it lands on olive — and olive is desaturation,
   which is the exact mechanism §L.2.1 blames for terracotta. Measured
   on the rendered fold in the lime state: at 0.40 mean saturation
   falls to 0.420 (every other state is 0.64-0.75) and 5.0% of pixels
   land in the beige quadrant (hue 8-50 at saturation < 0.45).

   0.32 is the shipped value. Same measurement: saturation back to
   0.479 and beige-quadrant pixels down to 1.8%. Going further to 0.26
   would reach 0.5%, but by then the five states stop being tellable
   apart, which costs the whole point. 0.32 keeps five legible rooms
   and keeps the weakest of them out of §B's grave. It also buys back
   contrast — see the build report. */
.loop__gel {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--gel);
  mix-blend-mode: color;
  opacity: var(--gel-opacity);
  /* THE SNAP, STATED EXPLICITLY AND DEFENSIVELY. The computed
     duration is already 0s, but base.css's §F.7 reduced-motion block
     re-declares transition-property on `*` to include background-color
     at var(--t-move) — a deliberate rule that keeps state changes
     legible everywhere else on the site, and exactly the wrong thing
     here, because a 380ms crossfade is precisely what this layer
     exists to replace. Declaring it on the class beats the universal
     selector on specificity in both directions, so the wash can never
     become a fade no matter what is added globally later. */
  transition: none;
}

/* §F.6 "The ground answers" — every strike lifts the floor and
   pushes a ripple out from the ring's centre. Both layers are pure
   opacity/transform, driven by the shared visual clock, so they run
   with sound off and never trigger layout. Kept deliberately quiet:
   the accent gets the full lift, the other three beats a third of
   it, so an unattended page breathes rather than strobes. */
/* The lift is INTENSITY, not colour — the film already supplies the
   colour, and a violet wash screened over a violet-magenta stage is
   a no-op. --chapter-on is the chapter's own ink and is #F2F4F8 in
   both themes, which is what a lantern coming up half a stop looks
   like on this footage. */
.loop__lift {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 62%;
  background: radial-gradient(70% 100% at 50% 100%, var(--chapter-on) 0%, transparent 66%);
  mix-blend-mode: screen;
  opacity: var(--lift);
  pointer-events: none;
}
/* The ripple IS colour: §L.6 says the ring lights in the colour of
   the beat being played, and the floor is the same instrument seen
   from underneath, so it answers in the same gel. loop.js writes
   --ripple-gel from the beat index, which is also why this is
   --beat-N (a glow — a permitted use of the raw gel, §L.4) and not
   --beat-N-text. It is never type. Defaults to the chapter gel, so
   the page still composes if the transport never fires. */
.loop__ripple {
  position: absolute;
  left: 50%;
  top: var(--ripple-y);
  width: 132vmin;
  height: 44vmin;
  margin-left: -66vmin;
  margin-top: -22vmin;
  border: 2px solid var(--ripple-gel);
  border-radius: var(--r-3);
  filter: drop-shadow(0 0 6px var(--ripple-gel));
  opacity: var(--ripple-opacity);
  transform: scale(var(--ripple-scale));
  pointer-events: none;
}
/* §F.7.3 — decorative ambience never mounts under reduce. The two
   layers above are declared here only, so under reduce they do not
   exist rather than being animated and then hidden. */
@media (prefers-reduced-motion: reduce) {
  .loop__lift,
  .loop__ripple { display: none; }
}

/* §K.4 lever (c): a little grain on top of real photographic
   material, not instead of it. */
.loop__grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image:
    repeating-linear-gradient(0deg, var(--chapter-on) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg, var(--chapter-on) 0 1px, transparent 1px 3px);
}

/* ---------- the title plate ----------
   §K.1: §D.5's composition has no headline slot — the ring is the
   display object and it is what has to occupy the frame. The §G
   headline is therefore set as a stage plate, and the ring runs at
   min(58vmin, 52vh) — 468px at 1440x900, roughly 2.8x the 168px
   ceiling K.1 sets for display type, and 56% of the stage height.
   Reported in full in the build report. */
/* ------------------------------------------------------------------
   DEFECT #15 — THE FALLOFF THIS PLATE WAS SET IN IS A PROPERTY OF ONE
   FRAME, NOT OF THE FILM.

   The rule this block used to carry ("held to the width of the plate's
   own deep falloff on the left") was derived from the poster. The
   poster is one frame of ten seconds of moving light, and the take's
   haze beams sweep the upper-left wall. Measured under the h1's own
   glyph mask, 24 frames across the full 10s loop x all five gel
   states, at the shipped 0.88 print: median background contrast runs
   4.76-11.1:1, but on the worst frame 43.8% of the h1's glyph pixels
   and 44.6% of the sub's sat on background under 4.5:1, brightest
   decile 3.22:1, worst single pixel 3.06:1. A single-frame capture
   passes only because the mean survives.

   WHY THE GRADE COULD NOT FIX IT ON ITS OWN. §K.2 lever (c) — print
   the plate down — is the lever this page already uses and the one
   /  and /about used successfully, so it was swept first, 24 frames x
   5 gels at each stop (h1 worst pixel / % of glyph pixels under 4.5 /
   K.4 ground band):

     0.88  3.06:1  43.8%   band 22.7      <- shipped
     0.85  3.25:1  40.3%   band 21.9
     0.72  4.29:1   3.2%   band 18.7
     0.62  5.41:1   0.0%   band 16.2
     0.60  5.65:1   0.0%   band 15.7
     0.58  5.85:1   0.0%   band 15.2
     0.55  6.28:1   0.0%   band 14.4

   The two constraints do not overlap. Zero failing glyph pixels first
   arrives between 0.62 and 0.64; the §K.4 floor of 18 on the ground
   band is lost below ~0.70. There is no stop that satisfies both, and
   the gap is not marginal — it is about four tenths of a stop. Grading
   to 0.62 would have traded a legibility defect for a flat-stage
   defect, which is the §K.4 failure this site was pulled back from
   once already. The print therefore stays at 0.88 and every exposure
   figure above this block still stands, unrevised.

   WHAT WAS DONE INSTEAD IS §K.2 LEVER (b): put the type over the dark
   region of the asset — the same lever the mobile block at the bottom
   of this file already applies to this same film. Lever (b) needs a
   region that is dark in EVERY frame, not in the poster, so the region
   was measured rather than chosen: the per-pixel MAXIMUM luminance
   across all 24 frames x 5 gels was accumulated into a map, and the
   whole stage searched for the largest box in which no pixel ever
   exceeds L=0.1607 (the luminance at which --chapter-on #F2F4F8 falls
   to 4.5:1). Two such boxes exist. The larger is the stage floor at
   the lower left — the band this file already names as "the darkest
   band in the whole take" where the key row sits, extended upward:
   x 40-360, y 500-770 at 1440x900 never exceeds L=0.097, i.e. never
   drops below 6.45:1, and over y 520-760 never below 8.5:1.

   So the plate moves onto the floor. It is not scrimmed, darkened,
   gradiented or shortened, and nothing is placed behind it — defect
   #12 is not being reintroduced by another route. The film is
   untouched. The type went to where the lanterns are not pointing,
   which on a page about stage light is the honest move.

   Consequences, all of them deliberate:
   - 320px, not 40ch. The dark box is 320 wide before a hot vertical
     streak at x~360-400 enters it on some frames; at 340 the worst
     frame reaches 3.87:1. The width is the measurement, not taste.
     The h1 therefore sets on two lines, which is why the ring — the
     display object this page actually leads with (§K.1 note above) —
     is unaffected.
   - position: absolute. Taking the plate out of the flex column is
     what keeps the change to one element: the stage keeps its three
     bands, and the instrument simply recentres in the space the title
     used to occupy at the top, moving the ring up by ~74px into a
     better-balanced frame. The chrome cluster, key row, counter and
     Play control do not move at all.
   - bottom-anchored. The dark region is the floor in front of the
     kit, so the plate is anchored to the bottom of the frame, which
     is the edge that region belongs to under any viewport height.
   The mobile block below restores static flow: at 390x844 a different
   26% of the film is on screen and that path is separately measured.
   ------------------------------------------------------------------ */
.loop__title {
  position: absolute;
  left: var(--gutter);
  bottom: calc(var(--sp-7) + var(--sp-5));
  z-index: 5;
  flex: 0 0 auto;
  max-width: 320px;
}
.loop__title h1 {
  font-size: clamp(4rem, 5.4vw, 5.0625rem);
  line-height: 0.92;
  letter-spacing: -0.03em;
  color: var(--chapter-on);
}
.loop__sub {
  margin-top: var(--sp-3);
  font-family: var(--font-mono);
  font-size: var(--t-1-size);
  line-height: var(--t-1-lh);
  letter-spacing: var(--t-1-track);
  color: var(--chapter-on);
}

/* ---------- the instrument ---------- */
.loop__instrument {
  position: relative;
  z-index: 4;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
}
.loop__ring fiv4-ring {
  --ring-size: min(58vmin, 52vh);
}
.loop__counter {
  font-size: var(--t-2-size);
  line-height: var(--t-2-lh);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--chapter-on);
}
.loop__play {
  padding: var(--sp-3) var(--sp-5);
}

/* ---------- SECTION 1 — chrome (§I row 18, Caldera's nav-morph) ----------
   A floating control cluster, not a card: it is the instrument's
   own hardware, radius 999 like every other control on the site.
   After ~3s idle the extras fade out and it collapses around the
   tempo readout; any pointer or key activity brings them back. */
.loop__chrome {
  position: absolute;
  top: calc(64px + var(--sp-3));
  right: var(--gutter);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--sp-2);
  max-width: min(64vw, 920px);
}

.loop__tempo {
  display: flex;
  align-items: baseline;
  gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--edge);
  border-radius: var(--r-3);
  background: var(--pad);
  cursor: ew-resize;
  touch-action: none;
  color: var(--ink);
  transition: color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.loop__tempo:hover,
.loop__tempo:focus-visible,
.loop__tempo.is-dragging { color: var(--accent); }
.loop__tempo-value {
  font-size: var(--t1-size);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.loop__tempo-unit {
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  color: var(--ink);
}

.loop__extras {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-2);
  transition: opacity var(--t-scene) var(--ease);
}
body[data-page="loop"].is-idle .loop__extras {
  opacity: 0;
  pointer-events: none;
}

.loop__grouping,
.loop__pattern {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
}
.loop__pattern-name {
  min-width: 11ch;
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--r-3);
  background: var(--pad);
  text-align: center;
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  color: var(--ink);
}

.loop__chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);
  min-height: 32px;
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--edge);
  border-radius: var(--r-3);
  background: var(--pad);
  /* --ink, not --ink-quiet: quiet ink on --pad measures 3.2:1 in the
     light theme, and every one of these is a control label (§K.2). */
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  transition: color var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease),
              background var(--t-hover) var(--ease);
}
.loop__chip:hover,
.loop__chip:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
}
.loop__chip--icon { min-width: 32px; padding-inline: var(--sp-2); }
/* The "on" state takes the chapter gel as a FILL, so it must carry
   --chapter-on and not --accent: --accent is the gel as TEXT and on
   this page that is #B79BFF, a lightened violet built to be read ON
   the ground, which as a fill would measure 1.9:1 against its own
   ink. --accent-fill / --chapter-on is the pair §L.4 exists for, and
   it measures 5.19:1 in both themes because both halves of it are
   theme-invariant. */
.loop__chip[aria-pressed="true"] {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}
.loop__chip[aria-pressed="true"]:hover,
.loop__chip[aria-pressed="true"]:focus-visible { color: var(--chapter-on); border-color: var(--accent-fill); }
.loop__chip--exit:hover,
.loop__chip--exit:focus-visible { color: var(--stop); border-color: var(--stop); }

/* ---------- SECTION 3 — the key row ----------
   The mapping is on screen at all times. The five strike keys are
   real buttons, which is also what the mobile collapse rule (§D)
   asks for: below 760px the same five controls become full-width
   beat pads. One component, two widths. */
.loop__keys {
  position: relative;
  z-index: 10;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}
.loop__pads {
  display: flex;
  gap: var(--sp-2);
}
.loop__pad {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 40px;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--edge);
  border-radius: var(--r-3);
  background: var(--pad);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  transition: color var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease),
              background var(--t-hover) var(--ease),
              transform var(--t-hover) var(--ease);
}
.loop__pad:hover,
.loop__pad:focus-visible { border-color: var(--accent); color: var(--accent); }
.loop__pad.is-struck {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}
@media (prefers-reduced-motion: no-preference) {
  .loop__pad:active { transform: scale(0.97); }
}

kbd {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  padding: 1px var(--sp-1);
  border: 1px solid var(--edge);
  border-radius: var(--r-1);
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--t-2-size);
  font-weight: var(--t-2-weight);
  line-height: 1.4;
}
.loop__pad kbd { border-color: currentColor; }

.loop__legend {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2) var(--sp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.loop__legend li {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  text-transform: uppercase;
  /* §K.2: the chapter's ink over the film. The key row sits on the
     darkest band in the whole take — the stage floor in front of the
     kit, mean luminance 0.010–0.018 raw across the entire gel cycle,
     before the 0.66 print — which is why this row and the counter
     clear 4.5:1 against the real moving pixels in both themes with a
     very large margin. Measured per frame, not assumed. */
  color: var(--chapter-on);
}

/* ---------- mobile collapse (§D) ---------- */
@media (max-width: 759px) {
  .loop {
    padding-top: calc(64px + var(--sp-3));
    padding-bottom: var(--sp-3);
    gap: var(--sp-2);
  }
  .loop__title h1 { font-size: var(--t3-size); }
  /* Back into the column: the phone's crop (see .loop__plate below)
     puts the whole upper half of the frame in the stage's own falloff,
     which is a measured pass at 390x844 and is not the desktop box. */
  .loop__title {
    position: static;
    max-width: none;
  }
  .loop__sub { max-width: none; }
  /* The chrome joins the column instead of floating, and the chips
     tighten so the whole cluster costs at most two rows — the stage
     still has to fit one viewport at 390x844 (§K.3). */
  .loop__chrome {
    position: static;
    order: 3;
    flex-wrap: wrap;
    justify-content: center;
    align-self: center;
    max-width: none;
    gap: var(--sp-1);
  }
  .loop__extras { justify-content: center; gap: var(--sp-1); }
  .loop__chip { min-height: 28px; padding: 2px var(--sp-2); }
  .loop__chip--icon { min-width: 26px; padding-inline: var(--sp-1); }
  .loop__tempo { padding: 2px var(--sp-2); }
  .loop__tempo-value { font-size: var(--t0-size); }
  .loop__pattern-name { min-width: 9ch; padding: 2px var(--sp-1); }
  .loop__instrument { gap: var(--sp-2); }
  .loop__play { padding: var(--sp-2) var(--sp-4); }
  .loop__keys { order: 4; }
  .loop__pads {
    width: 100%;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: var(--sp-1);
  }
  .loop__pad {
    flex-direction: column;
    gap: var(--sp-1);
    padding: var(--sp-2) var(--sp-1);
    min-height: 56px;
    justify-content: center;
  }
  .loop__pad-name { font-size: 10px; }
  .loop__legend { gap: var(--sp-1) var(--sp-3); }
  .loop__ring fiv4-ring { --ring-size: min(58vmin, 30vh); }
  /* THE PHONE TAKES A DIFFERENT PART OF THE PHOTOGRAPH, NOT A
     DIFFERENT EXPOSURE. A 390x844 portrait viewport covering a 16:9
     take matches on HEIGHT, so the whole vertical extent is shown
     and only ~26% of the width survives — object-position's Y has
     no effect at all here, and the desktop 1.26 print would leave
     nothing in frame but the bass drum. What the centred column
     leaves under the title is the beam funnel, the single brightest
     thing in the take: measured at 390x844 the headline scored
     2.33–4.26:1 across the gel cycle. Unshippable.

     The fix is §K.2 lever (b) — put the type over the dark region
     of the asset — applied on the axis the phone actually has.
     Anchoring the crop to the take's left edge takes the column
     that runs from the wing to the left crash: the beams cross it
     high and behind, so the whole upper half stays in the stage's
     own falloff. Same film, same 0.88 print, same ink; a different
     26% of the frame. Measured at 390x844, both themes, five gel
     phases: headline 10.99–14.17:1, sub 10.33–14.71:1, counter
     15.25–16.95:1, key row 13.34–14.89:1 — against 2.33:1 worst
     before. Nothing is scrimmed, darkened or shortened. */
  .loop__plate {
    transform: scale(1) translate3d(var(--plate-x), var(--plate-y), 0);
    object-position: 2% center;
  }
}

/* Short landscape viewports: keep the whole stage inside the fold. */
@media (max-height: 620px) {
  .loop__ring fiv4-ring { --ring-size: min(58vmin, 38vh); }
  .loop { padding-block: calc(64px + var(--sp-2)) var(--sp-2); }
}

/* ============================================================
   patterns.css — `/patterns` Pattern room
   Scoped under .patterns. Tokens only: see tokens.css.
   Source: docs/design-system-v2.md §D.3, §E.4, §F, §G, §H,
   the director's amendments §K.1–K.4, and §L (colour v3).

   COLOUR: repainted against §L. /patterns is chapter beat 3,
   lime #C7F53D — tokens.css sets --chapter/--chapter-text/
   --chapter-on from body[data-page="patterns"]. Every walnut-era
   alias this file used has been migrated out; there are none left
   and there are no literal hexes:

     --shell   -> --ground      as a surface
     --shell   -> --chapter-on  as ink on the chapter gel
     --head    -> --ink         primary voice
     --chrome  -> --quiet       secondary voice
     --ink-quiet -> --quiet
     --felt    -> --edge        hairlines, unlit arcs
     --brass   -> --accent      where it is TEXT
     --brass   -> --accent-fill where it is a FILL
     --patina  -> --accent-2 / a per-beat gel

   Per §L.4 a fill is never var(--accent) and text is never
   var(--accent-fill): that swap breaks AA in one theme or the
   other, and the one place this file deliberately inverts the
   pair (the hero Play control) is measured and commented there.

   §L.6 — where this page paints THE FIVE BEATS specifically, it
   uses --beat-1..5 rather than one flat chapter colour, so those
   elements run magenta -> cyan -> lime -> vermilion -> violet in
   step with the ring. On this page that is:
     * every card's 20-step figure  (step s belongs to beat s/4)
     * the grouping seam + the 3+2 / 2+3 label on each card
     * the bench, whose five columns ARE the five beats
   ============================================================ */

.patterns { display: block; }
@media (min-width: 760px) {
  .patterns { --section-gap: 152px; }
}

/* ------------------------------------------------------------
   1. Hero — the fold
   §K.4: the ground is a real material asset holding the whole fold
   at full strength (permitted lever (d)), not a tiled texture at
   22% multiply — which is a near no-op on a dark ground.
   THE FOLD NOW HAS A SUBJECT. It used to be plate-brass.webp, a
   recoloured sheet of metal: correct hue, correct type, and a flat
   green wall — measured ground-band stddev 22.6 against KPR's 63.9
   on the identical band. Flatness is fixed with material, not with
   chroma (§B.2, §K.4), so the sheet was replaced by an object in a
   room in the register `/about` and `/kit` already prove: sixteen
   drums held in a four-by-four steel rack, alone under four hard
   lime lanterns, real speculars on the chrome, a row of cast
   shadows on the lit floor, deep black falloff at both edges. The
   subject is the page's own argument — sixteen grooves, laid out
   as a grid. plate-brass.webp is untouched and still feeds cards
   01–04 and the /how manifest.
   ------------------------------------------------------------ */
.patterns__hero {
  position: relative;
  overflow: hidden;
  /* nav is 64px (§K.3) — hero + nav fill exactly the first viewport */
  min-height: calc(100dvh - 64px);
  display: flex;
  isolation: isolate;
  /* The stage UNDER the photograph is the chapter gel itself, not
     --ground — the same move `/` proved. --ground is theme-dependent
     and the plate is not, so a --ground stage swallows the near-black
     headline for the whole 350ms load delay in the dark theme.
     --chapter is theme-invariant and carries --chapter-on at 15.53:1
     by construction (§L.7), so the fold's ink is legible from the
     first painted frame and stays legible if the asset never arrives.
     It sits UNDER the photograph, full bleed, with no edge anywhere —
     it is a lantern flooding the stage, not a scrim. */
  background-color: var(--chapter);
}

.patterns__hero-plate {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Framing, and nothing but framing. The old plate was mirrored on
     its horizontal axis to bring its lit end under the type; that
     move belonged to an abstract sheet and is gone — this is a real
     room, and a room read upside down is a lie. What is left is a
     pan: the rack sits dead centre of the source frame and the copy
     column runs down the LEFT, so the frame is left alone: measured
     at 1440x900 the object-fit cover crop already lands the rack
     between the end of "in five." and the ring, with its cast
     shadows and the lit floor under the transport row, the four
     lanterns and their beams in the band above the headline — the
     band that was measured flat — and the black falloff down both
     edges. Three panned variants (scale 1.06/1.10/1.18 anchored
     top-left) were rendered and read side by side; every one of
     them clipped the rack's base and its shadows off the bottom of
     the fold, which is the part that says "room". A photograph that
     is already framed does not want a second framing.
     No mask: the image runs full bleed to the hero's own edge, the
     way `/`'s membrane does. The old bottom fade-out existed to hide
     a seam against --ground; with the gel stage behind it there is no
     seam to hide, and a masked fade is one more soft rectangle. */
  object-position: 50% 50%;
  /* §K.2(c) — a material-consistent grade applied to THE ASSET, and
     the whole reason this photograph can carry near-black display
     type at all. A real room under one hard lamp has deep falloff at
     both side edges, and the copy column runs down the LEFT edge:
     ungraded, 42.8% of the headline's ink pixels sat below 4.5:1
     against the pixels actually under the glyphs, worst 1.45:1.
     Measured, those failing pixels are not black — they land around
     0.19 sRGB — so a SHADOW LIFT clears them where a brightness
     multiplier cannot: brightness() scales, and scaling a dark pixel
     leaves it dark. contrast() below 1 pulls every channel toward
     0.5, which is an additive lift, and the brightness() after it
     puts the lanterns back. Measured on the rendered fold at
     1440x900, per-pixel under the glyph ink: worst 4.84:1, 1st
     percentile 5.06:1, and 0.00% of ink pixels below 4.5 — against
     the flat plate's own 1.54:1 worst / 4.51:1 p1 on the identical
     measurement, so this is an improvement on K.2, not a regression.
     Four grades were measured for the stddev they cost; this pair is
     the knee of that curve — the ground band above the headline
     still measures 45.3 (target 40, flat plate 25.7).
     saturate() here is a DISPLAY grade on the fold, restoring the
     chroma that the contrast reduction costs. It is not, and must
     not be confused with, a saturation boost applied to the asset to
     clear the §_gel_authenticity_gate — that operation is the exact
     artefact the gate exists to detect. The gate figures recorded in
     docs/assets.json are measured on the unfiltered .webp on disk.
     This is a filter on the image element, so there is no overlay,
     no panel and no rectangle, which is what defect #12 was about. */
  filter: contrast(0.48) brightness(1.58) saturate(1.25);
  opacity: 0;
  animation: patterns-plate-fade var(--t-scene) linear forwards;
  animation-delay: 350ms; /* §F.5 load choreography */
}
@keyframes patterns-plate-fade { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .patterns__hero-plate { opacity: 1; animation: none; }
}

/* §B.3 grain, raised amplitude per §K.4 lever (c). Ink-coloured hairlines
   at 5% in `overlay` — a texture, not a tint: it has no edge and no shape. */
.patterns__hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: overlay;
  background-image:
    repeating-linear-gradient(0deg, var(--ink) 0px, transparent 1px, transparent 3px),
    repeating-linear-gradient(90deg, var(--ink) 0px, transparent 1px, transparent 3px);
}

.patterns__hero-inner {
  position: relative;
  z-index: 2;
  /* §K.2 fix (a) — INVERT THE INK, and invert it theme-invariantly.
     The plate is one file served to both themes, so its ink cannot be
     a theme-dependent token; --shell (= --ground) used to sit here and
     resolved near-black on dark and near-white on light over an
     unchanged lime plate, so one of the two themes was always wrong.
     --chapter-on is the token that means exactly "the ink that goes on
     this page's gel": #0B0A12 in both themes, by design (§L.4). */
  color: var(--chapter-on);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: var(--sp-5);
  width: 100%;
  padding-block: var(--sp-6) var(--sp-7);
}

/* §E.4 — the signature at hero scale, fully inside the fold (§K.3) */
.patterns__hero-mark {
  position: absolute;
  bottom: var(--sp-6);
  right: var(--gutter);
  /* Token-to-token remap only, no new colours. The ring's hardware
     (unlit arcs, square, corner dots, counter) defaults to --edge and
     --quiet, both theme-dependent, and both disappear against a plate
     that is the same brightness in both themes. On the gel they take
     the gel's own ink. --chapter is remapped too so the accent pip and
     the "you are here" arc do not paint lime on lime; the five LIT
     arcs keep their own --beat-N gels (§L.6) and are untouched. */
  --edge: var(--chapter-on);
  --quiet: var(--chapter-on);
  --chapter: var(--chapter-on);
}
.patterns__hero-mark fiv4-ring { --ring-size: 26vmin; }

.patterns__hero-copy h1 {
  /* §K.1 — display type occupies the fold. 11.5vw is what this phrase,
     at this face, needs to clear ≥60% of viewport width at 1440. */
  font-size: clamp(64px, 11.5vw, 168px);
  line-height: 0.92;
  letter-spacing: -0.03em;
  /* greedy wrap, not balance: at this size balance leaves a short first
     line and shrinks the type's hold on the fold (§K.1). */
  text-wrap: wrap;
  color: var(--chapter-on);
}
.patterns__hero-sub {
  margin-top: var(--sp-4);
  max-width: 44ch;
  font-size: var(--t1-size);
  line-height: var(--t1-lh);
  color: var(--chapter-on);
}

/* the filter + transport row */
.patterns__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
}
.patterns__filter,
.patterns__grouping {
  display: inline-flex;
  gap: var(--sp-2);
}
/* Every control in this row carries its own opaque fill, so its contrast
   is decided by tokens and not by whichever pixel of the plate it lands
   on: --pad/--ink is 16.61:1 dark and 14.31:1 light. */
.patterns__filter-btn {
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-3);
  border: 1px solid var(--edge);
  background: var(--pad);
  color: var(--ink);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  text-transform: uppercase;
  transition: background var(--t-hover) var(--ease),
              color var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease);
}
/* --accent is the chapter gel as TEXT — correct for a hover ink and a
   1px border, both of which sit ON --pad and must clear AA there. */
.patterns__filter-btn:hover,
.patterns__filter-btn:focus-visible {
  border-color: var(--accent);
  color: var(--accent);
}
.patterns__filter-btn[aria-pressed="true"] {
  /* The selected chip inverts to ground-on-ink, which holds 16.61:1 in
     both themes and stays inside the token set. It deliberately does not
     take --accent-fill: lime-on-lime has no edge against the plate. */
  background: var(--ink);
  border-color: var(--ink);
  color: var(--ground);
}
.patterns__transport {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
}
/* The primary Play control sits ON the chapter gel, so the site's default
   .btn--accent (a --chapter fill carrying --chapter-on) would paint lime
   on lime and vanish. It takes the SAME PAIR THE OTHER WAY ROUND: the
   fill is --chapter-on and the ink is --chapter. Both hexes are
   theme-invariant, and the measured ratio is the identical 15.53:1 out of
   §L.7 — this is not the forbidden swap, which is putting --accent-fill
   on text over --ground/--pad, where it would be read against a surface
   that moves with the theme. Here the surface is the gel's own ink. */
.patterns__play.btn--accent {
  background: var(--chapter-on);
  border-color: var(--chapter-on);
  color: var(--chapter);
}
.patterns__play.btn--accent:hover,
.patterns__play.btn--accent:focus-visible {
  background: var(--chapter-on);
  border-color: var(--chapter-on);
  color: var(--chapter);
  opacity: 0.88;
}
.patterns__mute[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
.patterns__bpm {
  /* the tempo readout is hardware, not copy: it takes the same opaque
     chip as the other controls in the bar, which holds it clear of the
     plate in both themes. */
  padding: var(--sp-2) var(--sp-4);
  border-radius: var(--r-3);
  border: 1px solid var(--edge);
  background: var(--pad);
  color: var(--ink);
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  margin: 0;
}

@media (max-width: 759px) {
  /* §D mobile collapse — the ring moves above the headline */
  .patterns__hero-mark {
    position: static;
    align-self: flex-end;
    margin-bottom: calc(var(--sp-4) * -1);
  }
  .patterns__hero-mark fiv4-ring { --ring-size: 34vw; }
  .patterns__hero-inner { padding-block: var(--sp-5) var(--sp-6); gap: var(--sp-4); }
  .patterns__bar { gap: var(--sp-3); }
}

/* ------------------------------------------------------------
   2. The grooves — sixteen material plates, each a playable card
   The sixteen card plates arrived re-gelled in four families —
   lime, cyan, vermilion, violet, four cards each — and the grid
   already deals them out in that rotation, so scrolling the room
   walks the gels the same way the site walks them page to page.
   ------------------------------------------------------------ */
.patterns__grooves { padding-block: var(--section-gap); }
.patterns__section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
}
.patterns__status,
.patterns__bench-note {
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  color: var(--quiet);
  margin: 0;
}

.patterns__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}
@media (min-width: 760px) {
  .patterns__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1000px) {
  .patterns__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.patterns__cell[hidden] { display: none; }

.patterns__card {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 1px solid var(--edge);
  border-radius: var(--r-2);
  background: var(--pad);
  overflow: hidden;
  transition: border-color var(--t-hover) var(--ease);
}
.patterns__card:hover,
.patterns__card:focus-within {
  border-color: var(--accent);
}
.patterns__cell.is-playing .patterns__card,
.patterns__cell.is-live .patterns__card { border-color: var(--accent); }

.patterns__card-plate {
  position: relative;
  aspect-ratio: 16 / 7;
  overflow: hidden;
  background-color: var(--ground);
}
.patterns__card-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.patterns__card-index {
  position: absolute;
  left: var(--sp-2);
  bottom: var(--sp-2);
  padding: 2px 6px;
  border-radius: var(--r-1);
  /* an opaque chip, because the plates behind it run from a near-white
     lime highlight to near-black violet and no single ink covers both */
  background: var(--pad);
  color: var(--ink);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
}
.patterns__card-ring {
  /* §E.4 — the same ring at card scale (22px), sitting on the card's own
     panel rather than on the plate, where the plates' luminance runs from
     bright lime to near-black and the hardware would disappear. */
  margin-left: auto;
}

.patterns__card-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  padding: var(--sp-3);
}
.patterns__card-name {
  font-size: var(--t2-size);
  line-height: var(--t2-lh);
  letter-spacing: var(--t2-track);
  font-weight: var(--t2-weight);
  color: var(--ink);
}
.patterns__card-meta {
  display: flex;
  gap: var(--sp-2);
  margin-top: var(--sp-1);
  align-items: center;
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--quiet);
}
.patterns__card-blurb {
  margin-top: var(--sp-2);
  font-size: var(--t-1-size);
  line-height: 1.45;
  color: var(--ink);
}

/* ---- the 20-step figure: the grid is drawn in CSS, the hits in SVG ----
   §L.6 lives here. The figure is one bar: twenty sixteenths, four to a
   beat, so step s belongs to beat floor(s/4) and takes THAT beat's gel.
   Read left to right the row runs magenta -> cyan -> lime -> vermilion ->
   violet, which is the ring unrolled, and a hit's colour tells you which
   beat it lands on without counting.
   The -text variant, not the raw gel: these are 8px marks on --pad, at
   glyph scale and carrying the actual content of the card, and §L.7 is
   explicit that every raw gel fails as type on the pale ground (lime is
   1.15:1 there). --beat-N-text measures 5.14–14.42:1 dark and
   5.05–5.45:1 light on --pad. */
.patterns__figure {
  width: 100%;
  height: auto;
  margin-top: var(--sp-3);
  border-radius: var(--r-1);
  background-image:
    repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--quiet) 55%, transparent) 0 1px, transparent 1px 20%),
    repeating-linear-gradient(90deg,
      color-mix(in srgb, var(--edge) 60%, transparent) 0 1px, transparent 1px 5%),
    repeating-linear-gradient(0deg,
      color-mix(in srgb, var(--edge) 45%, transparent) 0 1px, transparent 1px 20%);
}
/* `color` + `currentColor` rather than `fill` directly, so one declaration
   per beat drives both the mark and the accent ring around it. */
.patterns .pf-hit {
  color: var(--quiet);
  fill: currentColor;
  transition: color var(--t-move) var(--ease);
}
.patterns .pf-hit[data-step="0"],
.patterns .pf-hit[data-step="1"],
.patterns .pf-hit[data-step="2"],
.patterns .pf-hit[data-step="3"]   { color: var(--beat-1-text); }
.patterns .pf-hit[data-step="4"],
.patterns .pf-hit[data-step="5"],
.patterns .pf-hit[data-step="6"],
.patterns .pf-hit[data-step="7"]   { color: var(--beat-2-text); }
.patterns .pf-hit[data-step="8"],
.patterns .pf-hit[data-step="9"],
.patterns .pf-hit[data-step="10"],
.patterns .pf-hit[data-step="11"]  { color: var(--beat-3-text); }
.patterns .pf-hit[data-step="12"],
.patterns .pf-hit[data-step="13"],
.patterns .pf-hit[data-step="14"],
.patterns .pf-hit[data-step="15"]  { color: var(--beat-4-text); }
.patterns .pf-hit[data-step="16"],
.patterns .pf-hit[data-step="17"],
.patterns .pf-hit[data-step="18"],
.patterns .pf-hit[data-step="19"]  { color: var(--beat-5-text); }

/* An accented hit is BIGGER, not a different hue — the hue is spoken for.
   A stroke in the mark's own colour grows it by 1.5px on every side, so
   the accent survives greyscale and reduced motion alike. */
.patterns .pf-hit.is-accent {
  stroke: currentColor;
  stroke-width: 3;
  stroke-linejoin: round;
}
/* the playhead's current step goes to full ink — the one mark that has to
   out-read every gel on the row, in both themes */
.patterns .pf-hit.is-now {
  color: var(--ink);
  stroke: currentColor;
  stroke-width: 2;
}

/* The grouping seam is the one mark on the card that names the SPLIT, so
   it takes the gel of the beat it opens: a 3+2 seam falls in front of
   beat 4 (vermilion), a 2+3 seam in front of beat 3 (lime). It is a
   1px rule on --pad, so it takes the -text variant with the hits. */
.patterns .pf-seam { fill: var(--quiet); }
.patterns__cell[data-grouping="3+2"] .pf-seam { fill: var(--beat-4-text); }
.patterns__cell[data-grouping="2+3"] .pf-seam { fill: var(--beat-3-text); }
/* the printed 3+2 / 2+3 label agrees with the seam it describes */
.patterns__cell[data-grouping="3+2"] .patterns__card-group { color: var(--beat-4-text); }
.patterns__cell[data-grouping="2+3"] .patterns__card-group { color: var(--beat-3-text); }

/* The sweep behind the playhead is a lit surface, not type, and nothing
   is ever inked on it — so it takes the raw chapter gel (§L.6) at low
   opacity and reads as light moving across the figure. */
.patterns .pf-head {
  fill: var(--chapter);
  opacity: 0;
  transition: opacity var(--t-hover) var(--ease);
}
@media (prefers-reduced-motion: no-preference) {
  .patterns__cell.is-playing .pf-head { opacity: 0.26; }
}

.patterns__card-ctas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
  margin-top: auto;
  padding-top: var(--sp-3);
}
.patterns__card-play { padding: var(--sp-1) var(--sp-3); }
/* a FILL, so it takes --accent-fill and the ink that answers to it */
.patterns__cell.is-live .patterns__card-play {
  background: var(--accent-fill);
  border-color: var(--accent-fill);
  color: var(--chapter-on);
}
/* TWO CLASSES, deliberately. base.css sets `a[href] { color: var(--ink) }`
   at (0,1,1), which silently outranks a single-class page rule at (0,1,0):
   a one-class `.patterns__card-open` never wears the colour declared here.
   Scoping to the CTA row takes it to (0,2,0) and the declaration lands.
   This is the only link this file colours, and it is checked. */
.patterns__card-ctas .patterns__card-open {
  font-family: var(--font-mono);
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 0.25em;
  /* The gel at 11px on the card panel is a hue, not a reading colour, so
     the affordance keeps --ink for the glyphs (16.61:1 / 14.31:1) and the
     chapter gel rides the underline, where contrast is not a reading
     requirement. Same rule as base.css, which is why hover swaps the
     underline to the standing second voice and thickens it: the state
     change never depends on colour alone. */
  color: var(--ink);
  text-decoration-color: var(--accent);
  text-decoration-thickness: 1px;
  transition: text-decoration-color var(--t-hover) var(--ease),
              text-decoration-thickness var(--t-hover) var(--ease);
}
.patterns__card-ctas .patterns__card-open:hover,
.patterns__card-ctas .patterns__card-open:focus-visible {
  color: var(--ink);
  text-decoration-color: var(--accent-2);
  text-decoration-thickness: 2px;
}

/* §F.6 the handoff — the chosen card's ring lifts as the page leaves */
@media (prefers-reduced-motion: no-preference) {
  .patterns__card-ring {
    transition: transform var(--t-move) var(--ease), opacity var(--t-move) var(--ease);
  }
  .patterns__cell.is-handoff .patterns__card-ring {
    transform: translate(-8px, -8px) scale(1.35);
    opacity: 0.35;
  }
}

/* ------------------------------------------------------------
   3. The bench — five voices down the side, five beats across
   §L.6 again, and this is the clearest case on the page: the five
   COLUMNS are the five beats, so column N lights in --beat-N. A
   pressed cell is a FILL and carries --beat-N-on; the ruler above
   it is TYPE and carries --beat-N-text. One column, two tokens,
   never one — collapsing them is the mistake tokens.css warns
   about for --accent / --accent-fill.
   ------------------------------------------------------------ */
.patterns__bench { padding-block: 0 var(--section-gap); }
/* the bench's standfirst sits under its heading, not opposite it */
.patterns__bench .patterns__section-head { display: block; }
.patterns__bench-sub {
  margin-top: var(--sp-2);
  color: var(--quiet);
  max-width: var(--measure);
}
.patterns__bench-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.patterns__bench-grid {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  flex: 1 1 auto;
  min-width: 0;
}
.patterns__bench-ruler,
.patterns__bench-row {
  display: grid;
  grid-template-columns: 56px repeat(5, minmax(0, 1fr));
  gap: var(--sp-2);
  align-items: center;
  margin: 0;
  max-width: none;
}
.patterns__bench-ruler {
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  color: var(--quiet);
}
.patterns__bench-ruler span { text-align: center; }
/* the beat numerals over the columns, each in its own beat's text gel */
.patterns__bench-ruler span:nth-child(2) { color: var(--beat-1-text); }
.patterns__bench-ruler span:nth-child(3) { color: var(--beat-2-text); }
.patterns__bench-ruler span:nth-child(4) { color: var(--beat-3-text); }
.patterns__bench-ruler span:nth-child(5) { color: var(--beat-4-text); }
.patterns__bench-ruler span:nth-child(6) { color: var(--beat-5-text); }
.patterns__bench-rowlabel {
  font-size: var(--t-2-size);
  letter-spacing: var(--t-2-track);
  font-weight: var(--t-2-weight);
  text-transform: uppercase;
  color: var(--ink);
}
.patterns__bench-cell {
  height: 44px;
  border-radius: var(--r-1);
  border: 1px solid var(--edge);
  background: var(--pad);
  transition: background var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease),
              transform var(--t-hover) var(--ease);
}
.patterns__bench-cell:hover,
.patterns__bench-cell:focus-visible { border-color: var(--accent); }
.patterns__bench-cell:active { transform: scale(0.96); }
/* a lit cell is a FILL: the raw gel, identical in both themes, exactly as
   the ring's lit arc is. Nothing is inked on it, so no -on token is needed
   until the "now" ring below, which is a border. */
.patterns__bench-cell[data-beat="0"][aria-pressed="true"] { background: var(--beat-1); border-color: var(--beat-1); }
.patterns__bench-cell[data-beat="1"][aria-pressed="true"] { background: var(--beat-2); border-color: var(--beat-2); }
.patterns__bench-cell[data-beat="2"][aria-pressed="true"] { background: var(--beat-3); border-color: var(--beat-3); }
.patterns__bench-cell[data-beat="3"][aria-pressed="true"] { background: var(--beat-4); border-color: var(--beat-4); }
.patterns__bench-cell[data-beat="4"][aria-pressed="true"] { background: var(--beat-5); border-color: var(--beat-5); }
/* the beat the clock is on: a --quiet ring on an unlit cell, and the gel's
   own ink on a lit one, so the marker is visible either way */
.patterns__bench-cell.is-now { border-color: var(--quiet); }
.patterns__bench-cell[data-beat="0"][aria-pressed="true"].is-now { border-color: var(--beat-1-on); }
.patterns__bench-cell[data-beat="1"][aria-pressed="true"].is-now { border-color: var(--beat-2-on); }
.patterns__bench-cell[data-beat="2"][aria-pressed="true"].is-now { border-color: var(--beat-3-on); }
.patterns__bench-cell[data-beat="3"][aria-pressed="true"].is-now { border-color: var(--beat-4-on); }
.patterns__bench-cell[data-beat="4"][aria-pressed="true"].is-now { border-color: var(--beat-5-on); }

.patterns__bench-side {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  flex: 0 0 auto;
}
.patterns__bench-ring { --ring-size: 120px; align-self: flex-start; }
.patterns__bench-controls {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.patterns__bench-field {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  margin: 0;
  max-width: none;
  font-size: var(--t-1-size);
  letter-spacing: var(--t-1-track);
  color: var(--ink);
}
.patterns__bench-range {
  flex: 1 1 160px;
  min-width: 120px;
  /* --accent, not --accent-fill: the slider's track and thumb sit on
     --ground and have to clear 3:1 there in both themes. */
  accent-color: var(--accent);
}
.patterns__bench-field output { color: var(--ink); }
.patterns__bench-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

@media (min-width: 760px) {
  .patterns__bench-body { flex-direction: row; align-items: flex-start; gap: var(--sp-7); }
  .patterns__bench-grid { max-width: 620px; }
  .patterns__bench-side { width: 340px; }
  .patterns__bench-cell { height: 56px; }
}

/* §F.7 reduced motion is a design, not a kill switch. The playhead sweep
   and the hit transitions stop; every colour the figure carries is a
   static property of the mark (which beat it lands on, whether it is
   accented), so the whole card is still readable with nothing moving. */
@media (prefers-reduced-motion: reduce) {
  .patterns .pf-hit { transition: none; }
  .patterns .pf-head { display: none; }
}
