/* ============================================================
   Chewtoy's Dugout — design tokens · "Night Pitch" (2026-09)
   Single source of truth for the redesign. Dark, flat, readable.

   SCOPE (migration only): eight names collide with the legacy
   css/core/variables.css (--font-body/display/mono, --gold-300/500/600,
   --z-sticky/toast). Until every page runs on v3 the tokens are declared on
   `html.ctd-v3` (a migrated page) and `.ctd-scope` (the new header/footer/
   notice wrappers on legacy pages) instead of the document root, so legacy
   pages keep their own values. At cutover: change the selector back to the
   root element and delete css/core/variables.css.
   ============================================================ */
/* Varsity Team Bold — the display face, and the same one the tags and
   nameplates are printed in. Declared here rather than in style.css because
   tokens.css is the only stylesheet that loads on every page of both trees.
   WOFF2 first (13 KB); the .otf that legacy style.css still points at is 68 KB
   and only serves a browser that cannot do WOFF2 — i.e. none of them. */
@font-face {
  font-family: "VarsityTeam";
  src: url("../../fonts/VarsityTeam-Bold.woff2") format("woff2"), /* lint-allow: url — a webfont source, not a texture */
       url("../../fonts/VarsityTeam-Bold.otf") format("opentype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400 900;
  font-style: normal;
  font-display: swap;
}

/* ------------------------------------------------------------------
   THE NINE THEMED PRINT FACES — the other half of the production
   generator's font catalogue.

   These are not site typography and must never be used as such. They
   exist so the builder can DRAW a part in the face it will actually be
   CUT in: `fonts/font_registry.json` in the Blender SprueGenerator is
   the authority, the order carries a stable id ("graveborn"), and the
   generator maps that id to the same .ttf byte-for-byte. Same file
   both ends is the whole point — a preview set in a lookalike would be
   a lie about a physical product.

   THEY COST NOTHING UNTIL ONE IS PICKED. A browser downloads a face
   only when something rendered actually uses the family, so declaring
   all nine here (677 KB in total, Metal Mania alone 239 KB of it) adds
   no bytes to a page that never opens the lettering panel. That is
   also why they are declared here rather than behind a builder-only
   sheet: a drawn part appears on the team-set product pages and in the
   cart too, and the face has to resolve wherever one is drawn.

   TTF, not WOFF2, and deliberately: these are the generator's own
   files, copied across unconverted so the two trees can be compared by
   digest. If a build step ever produces WOFF2 copies, keep the TTF as
   the second src so the digest check still has something to check.

   Licences ship beside them in fonts/licences/ — all nine are SIL OFL
   1.1 and the OFL requires the notice to travel with the font.
   ------------------------------------------------------------------ */
@font-face {
  font-family: "Creepster";  /* graveborn */
  src: url("../../fonts/Creepster-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Bowlby One SC";  /* ironjaw */
  src: url("../../fonts/BowlbyOneSC-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Germania One";  /* runeforged */
  src: url("../../fonts/GermaniaOne-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Marcellus SC";  /* elder_script */
  src: url("../../fonts/MarcellusSC-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Pirata One";  /* doomspire */
  src: url("../../fonts/PirataOne-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Eater";  /* rotblood */
  src: url("../../fonts/Eater-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Metal Mania";  /* verminscratch */
  src: url("../../fonts/MetalMania-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Cinzel";  /* imperial_crest */
  src: url("../../fonts/Cinzel-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Berkshire Swash";  /* hearthscribe */
  src: url("../../fonts/BerkshireSwash-Regular.ttf") format("truetype"); /* lint-allow: url — a webfont source, not a texture */
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

html.ctd-v3, .ctd-scope {
  /* ---- Ground & surfaces (the badge's purple) ----
     ROTATED TO THE LOGO, 2026-09-07 (owner: "change all the colours to look
     more like these"). The gutters had already been rotated on their own that
     morning, and the note in the --mist-* block below is the whole argument
     for why: sampled off assets/Chewtoys_Dugout_logo.webp, the painted panel
     sits at HUE 282-300 with a saturation of 0.24-0.30, and every ink and
     violet in the first cut of Night Pitch sat at 258-275 with the saturation
     falling away as the ramp lightened. Forty degrees apart is not a nuance.

     So the exception the mist block carved out is now the rule: the whole
     room is the badge's purple, and --mist-* is no longer a special case.
     LIGHTNESS IS UNTOUCHED at every stop — the value structure is what every
     contrast pair in tools/contract/css-tokens.mjs is computed from, and it
     was tuned separately from the hue. Saturation moved WITH the hue, because
     rotating alone leaves a ground that is purple by measurement and grey to
     look at. --ink-800 is the panel itself, one step down: what reads as the
     badge at 40mm reads a shade light across a whole card. */
  --ink-950: #18091A;   /* page ground */
  --ink-900: #241028;   /* alt section, header, inputs */
  --ink-800: #351D3A;   /* card surface · the badge's panel */
  --ink-700: #452A4B;   /* raised / hover surface */
  --line-1:  #54335B;   /* hairline on cards */
  --line-2:  #754A7D;   /* strong line, control borders */

  /* ---- Bone (text) ----
     LEFT WARM ON PURPOSE, and left where it was. The badge has no light
     purple in it: what is lit there is the gold, and everything purple is a
     ground. A cream text family is what the gold is legible against and what
     keeps a page of body copy from reading as a lilac wash. Only --bone-500
     moved, and only by two points of lightness, to hold AA over the lifted
     --ink-700. */
  --bone-100: #F2E9DA;  /* display + primary text  · 16.0:1 on ink-950 */
  --bone-300: #D6CAB6;  /* secondary text          · 12.0:1 on ink-950 */
  --bone-500: #AB9E92;  /* muted text              ·  5.8:1 on ink-800, 4.8:1 on ink-700 */

  /* ---- Accents (two, used sparingly) ----
     The gold is the badge's gold, which is a RAMP and not one hue held at
     three lightnesses: it runs H42 at the lit crown down to H31 in the bevel,
     warming and dropping saturation as it darkens, exactly as the metal in
     the logo does. The old ramp sat flat at H41-42 and read as sand. */
  --gold-300: #EED396;  /* prices, links · the lit crown of a letter */
  --gold-500: #D4A55E;  /* primary action fill · the face */
  --gold-600: #A6723A;  /* primary action hover · the bevel */
  --on-gold:  #18091B;  /* text on gold             ·  8.5:1 on gold-500, 5.4:1 on gold-600 */
  /* The light end of the badge's purple. Held to S56 rather than pushed with
     the rest: at this lightness the hue goes ORCHID above about S60, and a
     hot pink focus ring next to the badge is the exact mismatch this rotation
     was done to fix. */
  --violet-400: #D398E2;/* second headline line, info, focus · 9.0:1 on ink-950 */
  --scrim: rgba(24, 9, 26, 0.72); /* caption band over photos */
  --violet-600: #9549A2;
  /* The panel purple itself, for the one place that wants to BE the badge
     rather than be lit by it: the live nav plate. Declared here because it is
     used, not because the ramp looked short — see the loose-end warning in
     the pitch block. */
  --violet-700: #572F60;

  /* ---- Semantic ---- */
  --ok:   #7FB26F;  --ok-bg:   rgba(127, 178, 111, 0.14);
  --warn: #DDA74B;  --warn-bg: rgba(221, 167,  75, 0.14);
  --err:  #D56D62;  --err-bg:  rgba(213, 109,  98, 0.14);
  --info: var(--violet-400);

  /* ---- Role aliases (components use these, never raw hues) ---- */
  --bg: var(--ink-950);
  --bg-alt: var(--ink-900);
  --surface: var(--ink-800);
  --surface-2: var(--ink-700);
  --text: var(--bone-100);
  --text-2: var(--bone-300);
  --text-3: var(--bone-500);
  --accent: var(--gold-500);
  --accent-hover: var(--gold-600);
  --accent-2: var(--violet-400);
  --link: var(--gold-300);
  --link-hover: var(--bone-100);
  --focus: var(--violet-400);

  /* ---- Skill-category hues (chip dots; CB-mode overrides carry over) ----
     THE EIGHTH TOOK THE ONLY GAP LEFT. Seven of these sit at roughly 212°
     (blue), 142° (green), 17° (salmon), 43° (gold) and 282° (violet), plus two
     near-neutrals. That leaves two holes in the wheel — cyan around 175° and
     rose around 330° — and rose is the wider of the two: its neighbours are
     ~47° away either side, where cyan would crowd green and blue at ~35°. So
     Characteristic is rose, and no two category dots sit closer together than
     they did before it existed. */
  --cat-g: #9DB7D9;   /* General */
  --cat-a: #8FCFA6;   /* Agility */
  --cat-s: #E0907A;   /* Strength */
  --cat-p: #E5C97A;   /* Passing */
  --cat-m: #C79AE0;   /* Mutation */
  --cat-d: #B0B0B8;   /* Devious / other */
  --cat-t: #F2E9DA;   /* Trait (innate) */
  --cat-c: #E79BC0;   /* Characteristic (+1 stat tags) */

  /* ---- Type ---- */
  --font-display: "Oswald", "Barlow Condensed", "Arial Narrow", sans-serif;
  --font-body: "Barlow", "Segoe UI", system-ui, sans-serif;
  --font-mono: ui-monospace, "Cascadia Mono", Consolas, monospace;
  /* The face printed on the skill tags and base nameplates, so the page and
     the thing in the box speak with one voice. TITLES ONLY: it is a single-case
     slab (lowercase is drawn at cap height) and its counters fill in below
     ~20px — at the 12-14px of a nav link or an eyebrow it turns to mush. Every
     supporting level stays --font-display. Oswald is the fallback rather than a
     generic, so a failed font load degrades to the rest of the page. */
  --font-varsity: "VarsityTeam", "Oswald", Impact, sans-serif;
  /* THE FACE THE PARTS ARE CUT IN, and the one knob that changes it.
     js/ctd-parts.js draws every printed label through this token, so setting
     it on <body> repaints every tag, nameplate, stat line and marker on the
     page at once — no re-render, the same trick the base-paint planner uses.
     Unset it and the parts are Varsity, which is what ships by default and
     what every order placed before the lettering panel existed was cut in.
     The nine alternatives are declared at the top of this file; the panel
     that sets this lives in js/builder-v2/ui/print-font.js. */
  --font-print: var(--font-varsity);
  --fs-12: 0.75rem;  --fs-13: 0.8125rem; --fs-14: 0.875rem;
  --fs-16: 1rem;      --fs-17: 1.0625rem; --fs-18: 1.125rem; --fs-20: 1.25rem;
  --fs-24: 1.5rem;    --fs-28: 1.75rem;  --fs-32: 2rem;      --fs-40: 2.5rem;
  --fs-56: 3.5rem;
  --lh-tight: 1.05; --lh-snug: 1.2; --lh-body: 1.6;
  --track-caps: 0.08em; --track-display: 0.01em;
  --measure: 68ch;

  /* ---- Space (4px base) ---- */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 20px; --s-6: 24px;
  --s-8: 32px; --s-10: 40px; --s-12: 48px; --s-16: 64px; --s-24: 96px;

  /* ---- Shape & elevation (flat system: borders do the work) ---- */
  --r-1: 4px; --r-2: 8px; --r-pill: 999px;
  --bw: 1px;
  --shadow-1: 0 1px 0 rgba(0, 0, 0, 0.35);
  --shadow-2: 0 8px 24px rgba(0, 0, 0, 0.40);

  /* ---- Controls & layout ---- */
  --control-h: 44px; --control-h-sm: 40px; --control-h-lg: 52px; /* sm grows to 44px on touch widths */
  --wrap: 1200px; --wrap-narrow: 800px;
  --header-h: 64px; --tabbar-h: 64px; --actionbar-h: 68px; /* app pages swap the tab bar for the action bar */

  /* ---- Effects · the "after dark" layer (2026-09-03) ----
     Every effect is a NAMED TOKEN. `tools/contract/css-tokens.mjs` only lets a
     gradient / text-shadow / mask through a rule that resolves one of these, so
     the flat-system rule survives as "no ad-hoc effects" instead of "none".
     Hard limit: nothing here may sit behind body copy. Effects belong on
     grounds, edges, media and hover states — never under a paragraph. */

  /* Moonlight: a one-pixel highlight along the top edge of a raised surface.
     Two stops so a card and a header can differ without a second recipe. */
  --fx-rim:        inset 0 1px 0 color-mix(in srgb, var(--bone-100)  6%, transparent);
  --fx-rim-strong: inset 0 1px 0 color-mix(in srgb, var(--bone-100) 11%, transparent);

  /* Lift: the shadow a card casts when it comes off the pitch on hover. */
  --fx-lift: 0 10px 22px -14px color-mix(in srgb, var(--ink-950) 92%, transparent);

  /* Glows: ring + bloom in one shadow. Gold = action, violet = focus/info. */
  --fx-glow-gold:   0 0 0 1px color-mix(in srgb, var(--gold-500) 45%, transparent),
                    0 6px 20px -8px color-mix(in srgb, var(--gold-500) 55%, transparent);
  --fx-glow-violet: 0 0 0 1px color-mix(in srgb, var(--violet-400) 45%, transparent),
                    0 6px 22px -8px color-mix(in srgb, var(--violet-400) 45%, transparent);
  --fx-glow-price:  0 0 18px color-mix(in srgb, var(--gold-300) 32%, transparent);
  --fx-glow-headline: 0 0 26px color-mix(in srgb, var(--violet-400) 45%, transparent);
  --fx-glow-none:     0 0 0 transparent;   /* the resting end of a halo pulse */

  /* ---- The page ground ----
     A flat fill is what made the page read as bland. Four layers fix it, and
     every one of them is amplitude-capped (see DESIGN-SYSTEM.md §2): none
     shifts the ground luminance enough to move a contrast ratio.

     Blooms: three huge, deliberately asymmetric pools of light. Big enough
     that no edge is visible, so they read as depth rather than as shapes. */
  --fx-ground-bloom:
      radial-gradient(58% 42% at 14% 6%,  color-mix(in srgb, var(--violet-400) 9%, transparent) 0%, transparent 62%),
      radial-gradient(46% 38% at 88% 26%, color-mix(in srgb, var(--gold-300)   5%, transparent) 0%, transparent 64%),
      radial-gradient(72% 46% at 42% 96%, color-mix(in srgb, var(--violet-600) 7%, transparent) 0%, transparent 66%);

  /* Tooth: the grain that stops a dark ground looking like flat paint. Two
     dot grids at coprime sizes (3px and 7px) so the eye never resolves a
     pattern — a single grid moirés, two do not. */
  --fx-tooth:   radial-gradient(circle at 1px 1px,
      color-mix(in srgb, var(--bone-100) 2.4%, transparent) 0 0.6px, transparent 0.7px);
  --fx-tooth-2: radial-gradient(circle at 5px 2px,
      color-mix(in srgb, var(--bone-100) 1.7%, transparent) 0 0.6px, transparent 0.7px);

  /* Vignette: deepens the far corners so the content column sits in the light.
     Black rather than a plum token, because --ink-950 IS the ground and a
     vignette painted in it would be invisible by definition. */
  --fx-vignette: radial-gradient(128% 96% at 50% 34%,
      transparent 52%, color-mix(in srgb, black 42%, transparent) 100%);

  /* Card ground: lit from above, matching --fx-rim on the same edge. */
  --fx-card-ground: linear-gradient(180deg,
      color-mix(in srgb, var(--bone-100) 3.2%, transparent) 0%,
      transparent 38%);

  /* Turf: the pitch weave. 1px bone threads on a 6px diagonal, ~2% alpha —
     visible as a texture, invisible as a pattern. Grounds only. */
  --fx-turf: repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--bone-100) 2.4%, transparent) 0 1px,
      transparent 1px 6px);

  /* Touchline: the chalk boundary drawn inside an alt section's own border. */
  --fx-touchline: inset 0 5px 0 -4px var(--line-1), inset 0 -5px 0 -4px var(--line-1);

  /* Moon: the violet bloom behind the hero. Off-centre, top-right, no disc. */
  --fx-moon: radial-gradient(42% 56% at 78% 16%,
      color-mix(in srgb, var(--violet-400) 14%, transparent) 0%,
      color-mix(in srgb, var(--violet-600)  6%, transparent) 46%,
      transparent 72%);

  /* Sheen: the foil band that sweeps a primary button on hover. */
  --fx-sheen: linear-gradient(105deg,
      transparent 34%,
      color-mix(in srgb, var(--bone-100) 50%, transparent) 50%,
      transparent 66%);

  /* Claw: three parallel gouges, brightest first, raked across media on hover.
     Paired with --fx-claw-mask so they taper out instead of hitting the edge. */
  --fx-claw:
      linear-gradient(105deg, transparent 27%,   var(--fx-claw-lit) 27% 27.35%,   var(--fx-claw-cut) 27.35% 28.3%,   transparent 28.3%),
      linear-gradient(105deg, transparent 36.5%, var(--fx-claw-lit) 36.5% 36.8%,  var(--fx-claw-cut) 36.8% 37.6%,   transparent 37.6%),
      linear-gradient(105deg, transparent 43%,   var(--fx-claw-lit) 43% 43.25%,   var(--fx-claw-cut) 43.25% 43.9%,  transparent 43.9%);
  --fx-claw-lit: color-mix(in srgb, var(--bone-100) 78%, transparent);
  --fx-claw-cut: color-mix(in srgb, var(--ink-950) 82%, transparent);
  --fx-claw-mask: linear-gradient(180deg, transparent 0, black 18%, black 72%, transparent 100%);

  /* Fade line: a rule that dies out at both ends instead of stopping dead. */
  --fx-fade-line: linear-gradient(90deg, transparent 0, var(--line-2) 18%, var(--line-2) 82%, transparent 100%);

  /* ---- Ornament & motion (2026-09-03, round 2) ----
     The ornament system: one SVG component (.ctd-orn) placed by modifier,
     sized and timed from here. Mirrors the effect-token rule — a component
     never spells out its own duration, size or gradient. */
  /* ---- Metal ----
     Metal is a lit edge, a body and a shadowed edge on the same form. One flat
     stroke reads as a diagram no matter how good the outline is, so the corner
     ornaments carry these four tones and the SVGs consume them through var()
     rather than currentColor — which keeps one file recolouring per context the
     way currentColor did, while still letting a form be shaded.

     THE METAL IN THIS ROOM IS BRASS (2026-09-07). It was bone-grey iron, and
     iron is what the badge does not have: every hard edge in the logo — the
     frame, the spikes, the chain, the letters themselves — is one gold. The
     four tones are still mixed rather than picked, but from --gold-* darkened
     toward the ground instead of --bone-* lightened out of it.

     THE DIRECTION OF THE MIX IS THE WHOLE TRICK. Warming a purple ink with
     gold does NOT give brass: sRGB runs the two through a dead grey and comes
     out at H336-15, a dusty rose. Every metal tone below is therefore GOLD
     DARKENED — the hue is the gold's and stays the gold's, and only the value
     moves. Try it the other way and the frame goes pink. */
  --orn-metal:      color-mix(in srgb, var(--gold-600) 80%, var(--ink-800));
  --orn-metal-hi:   var(--gold-300);   /* the lit edge, upper-left */
  --orn-metal-lo:   var(--ink-950);    /* the shadowed edge and the seat */
  --orn-metal-spec: var(--bone-100);   /* the specular glint */

  /* A fastener is its own alloy. Every other piece of metal in this design
     draws the bolt in different tones from the plate it is driven through
     (--girder-rivet* on the frame, --joint-bolt* on the nameplates), so the
     gusset's bolts get their own three tones rather than borrowing the
     plate's. These defaults are the iron the divider ring is made of;
     ornament.css points a frame corner at the girder's steel and a `gold` one
     at the joints' bronze. */
  --orn-bolt:       color-mix(in srgb, var(--gold-600) 72%, var(--ink-800));
  --orn-bolt-hi:    var(--gold-300);
  --orn-bolt-lo:    var(--ink-950);

  /* ---- Pewter: the badge's THIRD metal ----
     The badge is not two colours, it is three. Sampling the neutral dark in
     it — outline and panel excluded, so this is the spikes and the chain —
     gives H300 S16-20 L12-27: a cool, desaturated plum pewter, about 100
     degrees off the gold and 30 points of saturation below it. That gap is
     what this ramp is for.

     WHY IT EXISTS AT ALL (2026-09-07, owner: "the brass on brass buttons
     blend too much, give them more variation in colour"). When the frame went
     brass, --fx-btn-steel went with it: it is mixed from --girder-*, so the
     secondary action became the third brass in a stack of brass — plate on
     frame on plate, all one hue at three lightnesses. A secondary control has
     to be a DIFFERENT metal, not a dimmer one.

     --pewter IS A LITERAL, on the same grounds as --slab, --turf and --grass:
     a mix cannot make one material out of another. Purple ink taken toward
     bone crosses through ROSE — every tone came out at H328-341, a dusty
     pink-grey — because bone is warm (H29) and the inks are cool-purple. So
     the face is declared and the bands come off it toward BLACK and WHITE,
     which is exactly how --flag-* comes off --slab, and which is what holds
     the hue at H294 the whole way down instead of letting it wander. */
  --pewter: #4C3B4E;
  --iron-lit:   color-mix(in srgb, var(--pewter) 74%, white);
  --iron-face:  var(--pewter);
  --iron-mid:   color-mix(in srgb, var(--pewter) 76%, black);
  --iron-shade: color-mix(in srgb, var(--pewter) 54%, black);
  --iron-lo:    var(--ink-950);
  /* The specular glint is the LIGHT, not the metal, so it stays warm bone on
     a cool alloy — which is also what stops the plate reading as grey plastic. */
  --iron-spec:  var(--bone-300);
  --iron-rivet:    color-mix(in srgb, var(--pewter) 82%, white);
  --iron-rivet-hi: color-mix(in srgb, var(--pewter) 56%, white);
  --iron-rivet-lo: color-mix(in srgb, var(--pewter) 64%, black);
  --iron-grain:    color-mix(in srgb, var(--pewter) 60%, black);
  --iron-grain-2:  color-mix(in srgb, var(--iron-lit) 22%, transparent);

  /* ---- Verdigris: back, and kept to the recesses ----
     Removed in the morning's rotation on the grounds that the badge has no
     green in it. The owner put it back the same day — "the verd stuff to be
     used around the place where it would suit, not overkill" — and the
     removal was the wrong call: verdigris is not a BRAND colour competing
     with the purple and the gold, it is what copper alloy DOES, and this room
     is now largely made of copper alloy. The badge has no rust in it either.

     NOT OVERKILL IS THE SPEC, so it is placed by chemistry and nothing else:
     only on brass or bronze (iron rusts, pewter does neither), only where
     water cannot run off — a bolt seat, a sunken field, the underside of a
     lip — and never on a face, where it stops being corrosion and becomes
     green paint. Three placements ship: the tee joints' recesses (its
     original home), the brass button's bolt seats and bottom lip, and the
     nameplate's sunken field. That is the budget; adding a fourth needs a
     reason better than "there is room".

     Darkened toward BLACK, not toward the ink: mixed into the purple ground
     it swings to H207 and comes out a blue-grey slate. Toward black it holds
     H157 at every stop. */
  --verd:       #4E9B7E;
  --verd-deep:  color-mix(in srgb, var(--verd) 62%, black);
  --verd-crust: color-mix(in srgb, var(--verd) 40%, black);
  --verd-lit:   color-mix(in srgb, var(--verd) 72%, var(--bone-100));
  /* The alphas it is actually placed at. Tune the look here rather than in
     the six --fx-* stacks that consume them. */
  --verd-seat:  color-mix(in srgb, var(--verd-deep)  62%, transparent);
  --verd-lip:   color-mix(in srgb, var(--verd-crust) 40%, transparent);
  --verd-bloom: color-mix(in srgb, var(--verd-lit)   22%, transparent);

  /* Ornament geometry. Fixed px, not a clamp — the divider's chain tiles a
     pattern at a fixed pitch and would drift against anything that scaled.
     Generated by the ornament script, which prints these values: change them
     there, not here.

     --orn-cog-c and --orn-cog-r used to live here, placing the belt that ran
     round a framed panel tangent to its four corner rings. Both went with the
     belt in 2026-09; the gusset needs no geometry beyond its own box, because
     it is one square SVG parked on a corner. */
  --orn-corner:    86px;                      /* corner ornament, 1:1 with its viewBox */
  --orn-corner-sm: 48px;                      /* card scale, and the phone size of a frame's */
  --orn-pitch:     12px;                      /* one chain link */
  --orn-chain:     11px;                      /* chain gauge, across the links */
  --orn-cap:       36px;                      /* divider ring, sized off the link pitch */

  /* Durations. The long ones are ambient and must never feel like a loop you
     can count; the short ones are responses to the pointer. */
  --t-glint:   5.5s;   /* light runs round a corner bracket */
  --t-breathe: 11s;    /* the moon */
  --t-trace:   2.6s;   /* light runs round a card border on hover */
  --t-shimmer: 7s;     /* highlight travelling a divider or a lit edge */
  --t-pulse:   4.5s;   /* headline glow breathing */
  /* A rattle is mostly stillness with a short burst in it, which is why this
     is long: the shake lives in the last ~14% of the cycle. Only the divider's
     ring caps rattle now — the frame corners are bolted and do not move. */
  --t-rattle:  7s;
  /* One link of travel. A sprocket advances the chain one tooth per 1/12 of
     a turn, so this is --t-cog / teeth and must stay that way: set it by
     hand and the chain visibly slips against the teeth it is sitting in. */
  --t-chain:   calc(var(--t-cog) / 12);
  --t-ground:  90s;    /* the page blooms breathing — barely perceptible on purpose */
  --t-reveal:  620ms;  /* a section rising into place */
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);

  /* Ring trace: a conic sweep, masked to the 1px border by --fx-ring-mask.
     --ctd-trace-angle is registered with @property in ornament.css so it can
     actually animate; without that registration it simply sits still. */
  --fx-trace: conic-gradient(from var(--ctd-trace-angle, 0deg),
      transparent 0turn,
      color-mix(in srgb, var(--gold-300) 92%, transparent) 0.05turn,
      color-mix(in srgb, var(--violet-400) 70%, transparent) 0.1turn,
      transparent 0.17turn, transparent 1turn);
  --fx-ring-mask: linear-gradient(black 0 0) content-box, linear-gradient(black 0 0);

  /* Shimmer: a narrow bright band that travels along a line. Sized by the
     rule that uses it (background-size), so one token serves the section
     divider, the lit section edge and anything else that wants a highlight
     running through it. */
  --fx-shimmer: linear-gradient(90deg,
      transparent 0%,
      color-mix(in srgb, var(--gold-300) 55%, transparent) 42%,
      color-mix(in srgb, var(--bone-100) 85%, transparent) 50%,
      color-mix(in srgb, var(--gold-300) 55%, transparent) 58%,
      transparent 100%);

  /* Lit section edge — replaces the rivet seam. A hairline that is brightest
     in the middle and dies out well before the corners. */
  --fx-edge-line: linear-gradient(90deg,
      transparent 0%,
      color-mix(in srgb, var(--violet-400) 30%, transparent) 22%,
      color-mix(in srgb, var(--gold-300) 42%, transparent) 50%,
      color-mix(in srgb, var(--violet-400) 30%, transparent) 78%,
      transparent 100%);

  /* ---- Stone ----
     The dungeon wall behind a hero or a footer. Generated by js/ctd-wall.js,
     fresh per page load, with assets/textures/wall-stone.svg as the no-JS
     fallback tile. The faces are the SAME surface tokens the cards are cut
     from, so the wall is this room's masonry rather than a fourth palette. */
  --stone-1: var(--ink-800);        /* face, most common */
  --stone-2: var(--ink-700);        /* face, catching the light */
  --stone-3: var(--ink-900);        /* face, in shade */
  /* One stone in twelve holds the torchlight. The only warm note in the wall,
     and the reason it does not read as one colour of grey — mixed from the
     accent that lights everything else here, not from a new hue. */
  --stone-4: color-mix(in srgb, var(--ink-800) 88%, var(--gold-300));
  --stone-mortar: var(--ink-950);   /* the joint: the page ground showing through */
  --stone-lit:      color-mix(in srgb, var(--bone-100) 13%, transparent);
  --stone-lit-soft: color-mix(in srgb, var(--bone-100) 6%, transparent);
  /* The cel layer: a hard-edged band inside each outline. Two of them, lit
     top-left and shaded bottom-right, are what give a flat polygon a form. */
  --stone-core:    color-mix(in srgb, var(--ink-950) 42%, transparent);
  --stone-plateau: color-mix(in srgb, var(--bone-100) 7.5%, transparent);
  /* Wear: chisel marks and age. */
  --stone-pit:     color-mix(in srgb, var(--ink-950) 40%, transparent);
  --stone-pit-lit: color-mix(in srgb, var(--bone-100) 5%, transparent);
  --stone-crack:   color-mix(in srgb, var(--ink-950) 62%, transparent);
  --stone-shade: color-mix(in srgb, var(--ink-950) 66%, transparent);
  --stone-mark:  color-mix(in srgb, var(--bone-100) 16%, transparent);

  --wall-claw: 34px;   /* the claw decal, at the scale of one stone face */
  /* Stones are px, not a viewBox, so one is the same size on every screen —
     which on a phone means one stone across a third of the display. This
     scales the painted wall down there instead. */
  --wall-scale: 1;
  --wall-amp: 0.55;    /* how far the wall comes up out of the ground */
  --t-torch: 6.5s;     /* one guttering cycle of the torch pool */
  /* One runnel of water down a drip, then a long rest. Ambient: slow enough
     that you catch it out of the corner of your eye rather than watching it. */
  --t-trickle: 11s;

  /* The wall dissolves rather than stopping at a line: solid where it starts,
     gone by the time the section hands back to the ground. */
  --fx-wall-fade: linear-gradient(180deg, black 0%, black 40%, transparent 96%);
  --fx-wall-fade-up: linear-gradient(0deg, black 0%, black 40%, transparent 96%);
  /* A mask that hides nothing — the resting end of the fade, the same idea as
     --fx-glow-none. Spelling it as a token rather than writing mask-image:none
     is what keeps tools/contract/css-tokens.mjs happy: that lint reads a bare
     mask-image declaration with no var(--fx-...) on the line as an ad-hoc
     effect, and rightly so. */
  --fx-wall-all: linear-gradient(black 0 0);

  /* Torchlight: the wall is lit from one place, so it is brightest there and
     falls into the dark at the edges. This is what stops a masonry ground
     reading as wallpaper. */
     The pool is WIDE on purpose. Tuned tight it looked right in a 390px lab
     bay and crushed the wall to nothing in a 644px hero — the falloff is
     relative to the box, so a tall section eats its own texture. */
  --fx-wall-torch: radial-gradient(88% 118% at 50% 0%,
      transparent 0%,
      color-mix(in srgb, var(--ink-950) 38%, transparent) 58%,
      color-mix(in srgb, var(--ink-950) 84%, transparent) 100%);

  /* The gutter walls: stone from the viewport edge in toward the measure.
     Masked so it dissolves as it approaches the content column — a hard
     vertical edge beside a paragraph reads as a border, and the join has to be
     invisible for the corridor to work. Mirrored per side. */
  --fx-wall-gutter-l: linear-gradient(90deg, black 0%, black 52%, transparent 100%);
  --fx-wall-gutter-r: linear-gradient(270deg, black 0%, black 52%, transparent 100%);

  /* Corridor light: a tall thin wall is lit from the corridor it faces, so the
     far edge falls away into the dark. Vertical falloff at both ends as well,
     so the stone does not stop dead at the top and bottom of the viewport. */
  --fx-wall-corridor-l: linear-gradient(90deg,
      color-mix(in srgb, var(--ink-950) 72%, transparent) 0%,
      color-mix(in srgb, var(--ink-950) 30%, transparent) 78%,
      color-mix(in srgb, var(--ink-950) 46%, transparent) 100%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--ink-950) 55%, transparent) 0%,
      transparent 24%, transparent 74%,
      color-mix(in srgb, var(--ink-950) 60%, transparent) 100%);
  --fx-wall-corridor-r: linear-gradient(270deg,
      color-mix(in srgb, var(--ink-950) 72%, transparent) 0%,
      color-mix(in srgb, var(--ink-950) 30%, transparent) 78%,
      color-mix(in srgb, var(--ink-950) 46%, transparent) 100%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--ink-950) 55%, transparent) 0%,
      transparent 24%, transparent 74%,
      color-mix(in srgb, var(--ink-950) 60%, transparent) 100%);

  /* Damp: water in the joints. The stain is ink; the gleam takes the violet
     the room is lit by, because a wet surface reflects the room and a dark
     streak on its own just reads as dirt. */
  /* The stain is painted TWICE, wide then narrow, so the effective alpha in
     the middle of a drip is about half again this value. */
  --stone-damp: color-mix(in srgb, var(--ink-950) 38%, transparent);
  --stone-wet:  color-mix(in srgb, var(--violet-400) 26%, transparent);
  /* Freshly broken rock, for the floor of a spall — the one place damage makes
     the wall LIGHTER instead of darker, and the reason a broken chunk reads as
     a break rather than a stain. */
  --stone-break: color-mix(in srgb, var(--ink-700) 90%, var(--bone-500));

  /* The hole where a stone has fallen out. Darker than --stone-mortar, which
     is the ground itself, or it reads as another dark stone instead of a gap.
     Black mixed into the ground rather than a new hue. */
  --stone-void: color-mix(in srgb, var(--ink-950) 72%, black);

  /* Scrim: the wall goes under display type only, and only with this over it. */
  --fx-wall-scrim: linear-gradient(180deg,
      color-mix(in srgb, var(--ink-950) 40%, transparent) 0%,
      color-mix(in srgb, var(--ink-950) 78%, transparent) 100%);

  /* ---- The pitch: the floor of the same room ----
     A Blood Bowl pitch paved in cracked stone squares, generated by
     js/ctd-ground.js and fixed behind the whole page. Same quarry as the wall,
     same light, shared damage tokens.

     IT IS A BACKGROUND AND NOTHING ELSE. The grass, the moss, the chalked
     touchlines and end zones and the standing water were all built and all
     taken back out: each was good on its own and each asked to be looked at,
     which is exactly what a page ground must not do. Their tokens are gone
     with them — --sod-green, --grass-*, --moss, --chalk*, --sod-wet,
     --sod-gleam — rather than left declared and unused, which is the loose-end
     pattern this file already warns about elsewhere.

     EVERY VALUE HERE IS WEAKER THAN THE WALL'S EQUIVALENT, and that is the
     point rather than an oversight. The wall lives in the gutters with no copy
     over it. The floor is the page ground: below the gutter breakpoint it is
     the entire backdrop, and above it, it shows inside the content column. It
     is a surface that gets READ THROUGH, so its relief is roughly half the
     wall's. --ground-amp on top of that. */

  /* ---- The stone: GREY. Four greys, and the purple only from the light ----
     The faces used to be mixed straight from --ink-800/900, which made the
     floor the same hue as the page and the whole thing read as one purple
     field. A plum MINORITY face was then tried as the "hint" and the owner
     called it (2026-09-05): it did not work. So the stone is its own material
     — a cool neutral grey — and the four face tones are four greys off it,
     mixed toward black, white and bone rather than toward the plum inks:
     dark, base, a cool mid and a warm pale. Whatever purple the floor shows
     is what the ground blooms drop on it from above, which is the right way
     round: a grey floor under violet light reads as a lit room; a violet
     floor reads as a colour wash. */
  --slab: #26262C;
  --flag-1: var(--slab);
  --flag-2: color-mix(in srgb, var(--slab) 76%, var(--bone-500));  /* warm pale: catching the light */
  --flag-3: color-mix(in srgb, var(--slab) 66%, black);            /* in shade — black, not the plum ink */
  --flag-4: color-mix(in srgb, var(--slab) 90%, white);            /* cool mid */

  /* The joint: packed dirt between the slabs, and the grid line. Neutral, and
     darker than every face so the squares read without any chalk on them. */
  --sod: color-mix(in srgb, var(--slab) 42%, black);
  /* Shadow in a joint, under a lifted edge. Black, for the reason
     --fx-vignette is black: the joint is already near the darkest thing here,
     so a shadow mixed from it would be invisible. */
  --sod-shade: color-mix(in srgb, black 46%, transparent);
  /* The hole where a slab has gone. NOT --stone-void, which is the wall's and
     is nearly black: a gap in a wall is a hole through into nothing, and a gap
     in a floor is a pothole with earth at the bottom of it. In --stone-void
     the missing slabs read as squares punched clean out of the page. */
  --sod-void: color-mix(in srgb, var(--sod) 62%, black);

  /* Relief. Compare --stone-lit 13% / --stone-shade 66%: a floor is seen
     face-on and lit from overhead, so its edges catch far less light than a
     wall's, and this is the surface with type over it. */
  --flag-lit:      color-mix(in srgb, var(--bone-100) 7.5%, transparent);
  --flag-lit-soft: color-mix(in srgb, var(--bone-100) 3.5%, transparent);
  --flag-shade:    color-mix(in srgb, var(--ink-950) 50%, transparent);
  /* THE CEL BEVEL: the two hard-edged bands inside a slab's outline that give
     a flat polygon its thickness — lit inside the top-left edges, shaded
     inside the bottom-right — same names and structure as --stone-plateau /
     --stone-core and --wood-plateau / --wood-core, because this room models
     form one way only. The floor went without its core band for a while,
     leaning on a wide edge stroke instead to save ~150 bytes a slab at 390
     slabs a screen; at 176px there are 40-130 and a slab with an outline and
     no band reads as a rubber gym tile. Both weaker than the wall's, for the
     read-through reason above. */
  --flag-plateau:  color-mix(in srgb, var(--bone-100) 4.5%, transparent);
  --flag-core:     color-mix(in srgb, var(--ink-950) 30%, transparent);
  /* One half of a fractured slab has settled: a shade over that half alone,
     so the two pieces are two tones and the break reads as a break rather
     than a line drawn across one stone. Faint — it is the difference between
     two adjacent face tones, not a new one. */
  --flag-settle:   color-mix(in srgb, var(--ink-950) 14%, transparent);
  /* The ledge where a corner has been knocked off: lower than the face, so
     darker than every face tone, but not the bottom of the joint — that is
     what made a chip indistinguishable from a big chamfer. */
  --flag-chip:     color-mix(in srgb, var(--slab) 30%, var(--sod));
  /* A patch scuffed pale by studs. A sheen, not a mark — at 5% a ROUND patch
     read as a blob of pale paint dropped on the slab. It is a long track now
     (patch() in js/ctd-ground.js), a shape that carries a little more before
     it reads as paint. */
  --flag-worn:     color-mix(in srgb, var(--bone-100) 4.5%, transparent);
  /* The dark counterpart: dirt ground INTO the face, the mud's earth at a
     fraction of the mud's alpha. Pale tracks alone left a face flat; a tone
     either side of the face tone is what reads as stone rather than paint. */
  --flag-stain:    color-mix(in srgb, var(--earth) 18%, transparent);
  /* Stud scratches and skids: a short score with a lit lip, the groove rule
     at its smallest. The lip does the reading — on dark stone a scratch is a
     pale line — so it takes the full --flag-lit; this is the score under it. */
  --flag-scratch:  color-mix(in srgb, var(--ink-950) 34%, transparent);
  /* Blood. It is Blood Bowl, and a pitch that is played on has it. DRIED: the
     earth pulled toward the palette red, never the red itself, at an alpha
     where a splatter is a dark warm stain you notice second. The generator
     places it in fill mode only — a splatter is a landmark, and a landmark in
     a tile is how an eye finds the period. */
  --gore:          color-mix(in srgb, var(--err) 40%, var(--earth));
  --blood:         color-mix(in srgb, var(--gore) 26%, transparent);
  /* Freshly broken rock, for the floor of a spall — the one place damage makes
     stone LIGHTER rather than darker. It cannot borrow the wall's
     --stone-break: that is mixed from --ink-700 and against grey paving it
     came out as a scatter of small violet blobs, which was the most visible
     thing on the floor and read as a rendering fault. Same idea, this stone. */
  --flag-break:    color-mix(in srgb, var(--slab) 74%, var(--bone-500));
  /* Chips and small stones collected in a joint. */
  --grit:          color-mix(in srgb, var(--bone-100) 6%, transparent);
  /* Mud dragged across the slabs. EARTH, NOT SHADOW: this was black at 34%,
     painted twice, and close up a smear on a pale slab compounded to ~56%
     black — a big dark wedge that read as a shadow with nothing casting it.
     Dirt has a colour and a shadow does not, so the earth is declared as a
     material of its own, the way --slab and --timber are, and the mud is that
     at an alpha where the darkest point of a smear is a smudge. Two passes,
     wide then a narrower one off-centre, never a third: drag marks inside the
     stain were tried and outlined it. */
  --earth:         #2B1F18;
  --mud:           color-mix(in srgb, var(--earth) 40%, transparent);

  /* How far the floor comes up out of the ground. Lower than --wall-amp for
     every reason in the block comment above. */
  --ground-amp: 0.62;
  --ground-scale: 1;   /* flags are px, so a phone gets fewer, bigger ones */

  /* Floodlit from above and behind the reader, so the far corners of the pitch
     fall away. This is what stops a floor reading as wallpaper, and it is the
     same job --fx-wall-torch does for the wall — turned through 90 degrees,
     because a floor's light comes from the near edge, not the top. */
  --fx-ground-pool: radial-gradient(120% 90% at 50% 4%,
      transparent 0%,
      color-mix(in srgb, var(--ink-950) 42%, transparent) 54%,
      color-mix(in srgb, var(--ink-950) 82%, transparent) 100%);

  /* ---- Timber: the boarding a section is lined with ----
     The third surface in the room, and the first one that is not stone. The
     wall and the floor are cut from one masonry kernel because they are the
     same rock; this is the boarding a dugout is lined with, generated by
     js/ctd-plank.js with assets/textures/plank-board.svg as the no-JS fallback
     tile.

     WHY A THIRD MATERIAL AT ALL. An alt section used to be the page ground at
     a different tint with the turf weave over it, and a tint is not a place. A
     boarded section reads as somewhere else in the same building, which is
     what the alternation was always trying to say.

     IT TAKES THE FLOOR'S DISCIPLINE, NOT THE WALL'S. Compare --stone-lit 13%
     and --stone-shade 66% with --wood-lit 8.5% and --wood-shade 55%: the wall
     lives in a gutter with nothing to read over it, and this is a section, so
     copy sits on it. --plank-amp on top of that. There is deliberately no
     boarding scrim, for the reason there is no `.ctd-ground--scrim`: a surface
     that needs one is a surface turned up too far.

     THE TIMBER IS ITS OWN HUE, AND IT IS WARM. Two things were tried and both
     are worth knowing. Mixing the faces from --ink made violet planks, which
     is the same failure the floor's block below records. Mixing them cold and
     nearly grey — so they would sit quietly under the room's violet light —
     fixed the hue and broke the material: it read as painted hardboard,
     because timber that has no colour of its own is not timber. What is here
     is old creosoted softwood, warm and dark, and the room's violet arrives on
     it from the blooms rather than out of the boards. */
  --timber: #33241C;
  --wood-1: color-mix(in srgb, var(--timber) 92%, var(--ink-950));   /* face, most common */
  --wood-2: color-mix(in srgb, var(--timber) 84%, var(--bone-500));  /* face, catching the light */
  --wood-3: color-mix(in srgb, var(--timber) 62%, var(--ink-950));   /* face, in shade */
  /* The two boards that are not like the others, and one board in eleven and
     one in twenty. --wood-4 is the board the lantern found — the trick
     --stone-4 plays in the wall, and the reason a row of timber does not read
     as one brown. --wood-5 has been burnt: the near-black slat that gives the
     row somewhere to fall away to. NEITHER OF THEM IS PURPLE any more; the
     damp board that used to sit here took the room's violet into the timber
     and it read as a bruise. */
  --wood-4: color-mix(in srgb, var(--timber) 86%, var(--gold-300));
  --wood-5: color-mix(in srgb, var(--timber) 42%, black);

  /* The gap between two boards is not a painted line, it is what is BEHIND the
     boarding — so it is the page ground, and it can never come out lighter
     than a board whatever the boards are doing. Same trick as --stone-mortar.
     What the reader actually sees is wider than the nominal gap and varies
     down its length, because both edges facing it have been worn back. */
  --wood-joint: var(--ink-950);
  /* Where a board has gone entirely. Below the joint, or it reads as another
     dark board rather than as the absence of one. */
  --wood-void: color-mix(in srgb, var(--wood-joint) 62%, black);

  /* Relief. Lit from above, like every other surface in this room — and both
     strokes follow the board's WORN edge rather than a straight line, which is
     what stops the eye finding the rectangle underneath. */
  --wood-lit:      color-mix(in srgb, var(--bone-100) 8.5%, transparent);
  --wood-lit-soft: color-mix(in srgb, var(--bone-100) 3.8%, transparent);
  --wood-shade:    color-mix(in srgb, var(--ink-950) 55%, transparent);

  /* THE CEL BEVEL, and it is what makes a board read as a slab rather than as
     a shape with a highlight on it. A hard-edged band inside the outline: lit
     along the top and the left end, shaded along the bottom and the right.

     SAME NAMES AND SAME STRUCTURE AS --stone-plateau / --stone-core, because
     it is the same idea and this room only has one way of doing it — every
     surface here gets its form from hard-edged layers rather than from a blur,
     and the stone's own block says why. Two more paths a board, and it is
     worth more than everything in the grit block below put together.

     The core is the deeper of the pair: a board's underside faces away from a
     lamp that is overhead, where its top face only tilts away from it. */
  --wood-plateau: color-mix(in srgb, var(--bone-100) 5.5%, transparent);
  --wood-core:    color-mix(in srgb, var(--ink-950) 30%, transparent);

  /* The grain. --wood-figure is the one line a board draws as a tapered MARK
     rather than a stroke, so it can start wide and die away; the rest are
     0.9px strokes, which is what fine grain actually is. */
  --wood-grain:     color-mix(in srgb, var(--ink-950) 46%, transparent);
  --wood-grain-lit: color-mix(in srgb, var(--bone-100) 5%, transparent);
  --wood-figure:    color-mix(in srgb, var(--ink-950) 32%, transparent);

  /* Knots. The core is nearly black because a knot IS a hole in the timber's
     story, and the lit arc is the upper half only: branch wood is denser and
     wears slower, so a knot stands proud of a weathered board. */
  --wood-knot:      color-mix(in srgb, var(--ink-950) 62%, black);
  --wood-knot-ring: color-mix(in srgb, var(--wood-3) 72%, black);
  --wood-knot-lit:  color-mix(in srgb, var(--bone-100) 7%, transparent);

  /* ---- Grit ----
     What a century leaves on a board, and the pass that decides whether the
     surface survives being looked at closely.

     EVERY ONE OF THESE HAS AN EDGE, and that is the rule they replaced a
     mistake to get. What used to be here was a pale wavy patch and a violet
     damp bloom, both wide soft tapered ribbons — and a big soft blob is the
     one shape that cannot look like anything on a piece of timber, so both
     read as a smear on the lens rather than as wear on the board. A saw mark,
     a worm hole, a scratch and a split all have a hard boundary, which is why
     they read as damage at a tenth of the area.

     --wood-saw runs ACROSS the board — the only mark here that does. A surface
     whose every mark runs the same way is a surface of stripes.
     --wood-scuff is the only BRIGHT mark on the face: dirt taken off rather
     than put on. A dark surface with nothing bright on it flattens. */
  --wood-split: color-mix(in srgb, var(--ink-950) 62%, transparent);
  --wood-saw:   color-mix(in srgb, var(--ink-950) 28%, transparent);
  --wood-grit:  color-mix(in srgb, black 52%, transparent);
  /* Torn fibre: hundreds of 3-16px hairs running along the grain. Weaker than
     the grain itself, because a fibre is a splinter standing off the board and
     the grain is a groove cut into it — and at this many marks, an alpha that
     looks right on one of them is a fog over the whole face. */
  --wood-fibre: color-mix(in srgb, var(--ink-950) 34%, transparent);
  --wood-scuff: color-mix(in srgb, var(--bone-100) 7%, transparent);

  /* Fixings. Iron in wet timber bleeds into the grain it is sitting in, so the
     weep runs ALONG the board — and this is the one place the semantic --err
     earns a decorative job, because rust is exactly that colour and mixing a
     second one would be a hue nobody asked for. */
  /* THREE PARTS TO A NAIL, and it needs all three to read as iron sunk into
     timber rather than as a dot. --wood-nail-hole is the countersink, painted
     as a disc offset down and right of the head: it is the shadow the head
     casts inside the hole it was driven into, and it is the only one of the
     three that says the head is BELOW the face of the board. Then the iron
     itself, and a specular pin on its upper left.

     There was a fourth, a lit crescent along the head's upper edge, and it is
     gone with its token: at three or four pixels across it was invisible under
     the specular pin sitting on top of it, and the fixings were 27% of the
     surface's markup. */
  --wood-nail-hole: color-mix(in srgb, var(--ink-950) 66%, transparent);
  --wood-nail:      color-mix(in srgb, var(--bone-500) 46%, var(--ink-950));
  --wood-nail-rust: color-mix(in srgb, var(--err) 30%, var(--ink-950));
  --wood-nail-spec: color-mix(in srgb, var(--bone-100) 38%, transparent);

  /* Something came through the boarding. The site's claw, cut in rather than
     raked over — black, not ink, because the gouge goes through the board and
     out the far side. Fill mode only: it is a landmark, and a landmark inside
     a repeating tile is how an eye finds the tile's period. */
  --wood-gouge:     color-mix(in srgb, black 82%, transparent);
  --wood-gouge-lit: color-mix(in srgb, var(--bone-100) 13%, transparent);

  /* How far the boarding comes up out of the section it lines. Between
     --ground-amp and 1: this surface is smaller than the floor and nearer the
     reader, but it still has copy on it. */
  --plank-amp: 0.72;
  --plank-scale: 1;   /* boards are px, so a phone gets fewer, bigger ones */

  /* The lantern over the section. Without a light source in it an even field
     of texture is wallpaper — the same sentence as --fx-wall-torch and
     --fx-ground-pool, and the same fix. It is also what breaks the fallback
     tile's period, because the darkening at the far ends is a gradient and a
     gradient has no period.

     The warm stop is doing real work and is not decoration: a lamp over dark
     timber is the reason the boards nearest it look like wood at all, and it
     is held at 5% because past about 8 the top of every section starts to read
     as a light source in its own right. */
  --fx-wood-lamp:
      radial-gradient(72% 62% at 50% -10%, color-mix(in srgb, var(--gold-300) 5%, transparent) 0%, transparent 72%),
      radial-gradient(104% 136% at 50% -8%,
        transparent 0%,
        color-mix(in srgb, var(--ink-950) 34%, transparent) 54%,
        color-mix(in srgb, var(--ink-950) 80%, transparent) 100%);

  /* Boarding runs to the edges of the box it lines — a section is a room with
     boarded walls, not a plank floating on a page — so there is no mask by
     default. This is for the placement that wants one, and --fx-wood-all is
     its resting end, spelled as a token for the reason --fx-wall-all is. */
  --fx-wood-fade: linear-gradient(180deg, transparent 0%, black 12%, black 88%, transparent 100%);
  --fx-wood-all:  linear-gradient(black 0 0);

  /* ---- Turf: the pitch itself ----
     Grass, generated by js/ctd-turf.js, for a placement that WANTS the pitch:
     a hero, a product bay, a masthead. It is the fourth surface in the room
     and the only one that moves.

     IT IS NOT THE PAGE GROUND, and the distinction is the whole licence for it
     existing. The floor above had its grass built and then taken back out
     because a page ground must not ask to be looked at — that ruling stands,
     and this is the other case: a surface the reader is meant to LOOK AT
     rather than read through. So --turf-amp sits above --ground-amp and
     --plank-amp, and the rule that comes with it is blunt: no body copy over
     this at any amplitude. Display type on a scrim, or a card.

     TWO MATERIALS ARE DECLARED, the way --slab and --earth are, because a mix
     cannot make one out of the other: --turf is sod seen from ABOVE, dark and
     low-chroma, and --grass is a blade seen EDGE-ON with light down it. Every
     other green here is a mix of those two, so the pitch cannot drift out of
     one family however it is tuned.

     GREEN, BUT THIS ROOM'S GREEN. The pitch is a night pitch: the sod is
     darker than any real turf, because a lit-daylight green next to
     --ink-950 stops being a surface in a room and becomes a photograph
     someone has pasted onto one. What makes it read as grass is not the
     saturation, it is that the green STOPS — at a torn edge, a divot, a
     bare patch of --earth. */
  --turf:  #1C2A19;   /* sod, from above */
  --grass: #3D5A2A;   /* one blade, edge-on to the light */

  /* The mown stripe. The strongest pitch cue there is and the cheapest: one
     tone, in bands, over the sod. Kept close to --turf — a stripe you can
     read the EDGE of is a stripe that has become a pattern. */
  --turf-mow:  color-mix(in srgb, var(--turf) 82%, var(--grass));
  /* Sod having a different year: a damp patch and a patch gone over to straw.
     The straw one is the only warm note in the green, and it is what stops the
     field reading as one flat colour with things drawn on it. */
  --turf-damp: color-mix(in srgb, var(--turf) 89%, black);
  --turf-dry:  color-mix(in srgb, var(--turf) 94%, var(--gold-300));
  /* Where the grass has gone: the pitch's own earth, and the dark at the
     bottom of a divot. --earth, NOT a brown of its own — it is the same dirt
     the mud on the paving next door is made of, and a second one at a slightly
     different hue is how two surfaces stop looking like one place. */
  --turf-bare: color-mix(in srgb, var(--earth) 68%, var(--turf));
  --turf-hole: color-mix(in srgb, var(--earth) 46%, black);
  /* Mud, and the light on it. --mud itself is the shared one — the same dirt
     the paving next door carries — but the WET part of a churned hollow is
     turf's own: dragged mud on stone is a thin film with no depth to be wet
     in. The sheen is the room's rule for a hole, unchanged: a hollow takes its
     light on the bottom-right, which is exactly where standing water is. */
  --turf-mud-wet:   color-mix(in srgb, var(--turf-hole) 50%, transparent);
  --turf-mud-sheen: color-mix(in srgb, var(--bone-100) 7%, transparent);
  /* The cel bevel, in this material's tokens: a lit band inside the top-left
     edges of anything raised and inside the bottom-RIGHT of anything sunk,
     with the shade opposite it. Same structure as --flag-plateau/--flag-core
     and --wood-plateau/--wood-core, because this room models form one way.
     Weaker than the floor's: turf has no edge to catch light the way cut stone
     does, so a hard rim reads as a sticker rather than as a lip of sod. */
  --turf-lit:   color-mix(in srgb, var(--bone-100) 8%, transparent);
  --turf-shade: color-mix(in srgb, var(--ink-950) 44%, transparent);

  /* THE MOWER'S GRAIN — the lie of the grass inside a stripe, and what stops
     the two greens being two greens. Light rather than a colour, and a fifth
     the strength of the crescents above: a bevel has to say which way a lip of
     sod faces, the grain only has to stop a band being flat.

     THE TWO ARE NOT THE SAME NUMBER AND CANNOT BE. --ink-950 is barely darker
     than --turf and about as saturated, so 9% of it over the sod moved the
     green by three values and the dark stripes stayed exactly as flat as they
     were — the whole point of the pass, missed, on a number that looked
     symmetrical. Bone is a long way from the sod, so 3.6% of it does more work
     than 26% of ink. Judge the two by eye against each other, never by the
     percentages. If the pitch ever has to go quiet under something, take them
     to 0 before touching --turf-amp: the grain is what makes it busy now, not
     the damage. */
  --turf-nap:      color-mix(in srgb, var(--bone-100) 3.6%, transparent);
  --turf-nap-dark: color-mix(in srgb, var(--ink-950) 26%, transparent);

  /* The blades. A tip catching the floodlight, and the one clump in six that
     has gone to straw — the same warm note as --turf-dry, an octave up,
     because it is a mark of a few square pixels and has to survive being that
     small. */
  --grass-lit: color-mix(in srgb, var(--grass) 84%, var(--bone-100));
  --grass-dry: color-mix(in srgb, var(--grass) 82%, var(--gold-300));

  /* How far the pitch comes up out of the ground. HIGHER than --ground-amp and
     --plank-amp, and the note above says why: this is a surface to look at.
     The discipline is in what goes over it, not in flattening it. */
  --turf-amp: 0.88;
  --turf-scale: 1;   /* blades are px, so a phone gets fewer, bigger ones */

  /* THE WIND. The blades lean, in groups, out of phase — the only generated
     surface in the room that moves, and js/ctd-turf.js emits the cells and a
     phase per cell precisely so that all three numbers live HERE and can be
     cancelled outright by prefers-reduced-motion.

     --turf-lean is the angle ONE CLUMP leans to either side of the drawn
     geometry, so the still frame is the middle of the sway and the total travel
     is twice this. It was 2.6deg while the groups were cells of the surface
     rather than clumps — a cell caps the angle, because everything near the top
     of it gets dragged sideways at its base — and 2.6deg on a 25px blade is one
     pixel of tip travel: a number you can measure and cannot see. Per clump
     there is nothing above the origin but the blades, so it is nine degrees
     either side and a 34px clump's tip travels about ten pixels across the
     swing — a quarter of its own height, which is a breeze rather than a
     tremor. Each clump scales it by its own --a (0.55-1.35), so the field is
     never one wind speed.
     --t-wind is one lean and back, before each clump's own --s multiplies it
     into the 3.6-7s the field actually runs at. */
  --turf-lean: 9deg;
  --t-wind: 5s;
  /* A sine in and out. --ease is the room's interaction curve and it is
     deliberately asymmetric — fast out, slow in — which is right for a
     thing arriving and wrong for a thing oscillating: on an alternating
     animation it snaps at one end of every swing. Wind does not snap. */
  --ease-wind: cubic-bezier(0.45, 0, 0.55, 1);

  /* Floodlit from the near edge, like the paving and for the same reason: an
     even field of texture with no light source in it is wallpaper. This is
     also what breaks the fallback tile's period, because a gradient has none. */
  --fx-turf-pool: radial-gradient(118% 94% at 50% 0%,
      transparent 0%,
      color-mix(in srgb, var(--ink-950) 38%, transparent) 56%,
      color-mix(in srgb, var(--ink-950) 84%, transparent) 100%);
  /* There was a --fx-turf-gust here: a wide, weak band of light that crossed
     the pitch on a slow translate, as the half of the wind that reads at a
     distance. It is gone with its keyframes (2026-09-05, owner). While the
     blades were leaning by a degree in cell-sized groups it was the only
     motion anyone could see, so the surface read as a filter sweeping over a
     still picture of grass — the exact words were "not the filter over the
     top". Once the blades move properly it has no job, and a light sweep over
     moving grass is a second weather system. Removed rather than left
     declared and unused: the loose-end rule this file states elsewhere. */
  /* THE GUTTERS ARE NOT GRASS ANY MORE (2026-09-07, owner). They run
     js/ctd-mist.js — see the --mist-* block below — and the grass keeps the
     home teams bay on index.php, which is the placement it was always best at.
     --fx-turf-gutter-l/r and --fx-turf-corridor-l/r went out with the move
     rather than being left declared and unused: the loose-end rule this file
     states elsewhere. Everything else in this block still has a caller. */

  /* The pitch runs to the edges of its box by default; this is for the
     placement that wants it to die out instead, with --fx-turf-all as the
     resting end, spelled as a token for the reason --fx-wall-all is. */
  --fx-turf-fade: linear-gradient(180deg, black 0%, black 58%, transparent 100%);
  --fx-turf-all:  linear-gradient(black 0 0);

  /* ---- Mist: the gutters ----
     Violet air turning over on itself down both sides of the page, generated
     by js/ctd-mist.js. The sixth surface in the room and the second that
     moves.

     IT HAS NO SUBJECT, and that is the whole design. A gutter is in the corner
     of the eye for as long as a page is open, so anything you can NAME in it
     is a thing that will be looked at, every time. Four alternatives were
     built before this one — a crowd, a wood, a fence, stained glass — and all
     four were turned down for exactly that. Weather has nothing to name.

     WHICH IS ALSO WHY THIS NEEDS NO AMPLITUDE RULE. --turf-amp exists because
     the pitch is a surface you are meant to look AT, so it carries a blunt
     "no body copy over this at any amplitude". There is no such rule here: the
     gutters are outside the measure, nothing reads over them, and there is
     nothing in the surface that competes for attention even if something did.

     The five dials below are read at build time by js/ctd-mist.js off the
     component's own computed style, so the surface can be retuned from this
     file without opening the generator. The values shipped are the "Churn"
     recipe the owner chose on 2026-09-07. */
  /* THE LOGO'S PURPLE, not the theme's violet (2026-09-07, owner: "could we
     get it to look like the purple on the logo"). They were never the same
     colour and the gutters were the place it showed: sampled off
     assets/Chewtoys_Dugout_logo.webp, the painted panel sits at HUE 298-300
     with a saturation of 0.20-0.33, and every violet in Night Pitch —
     --violet-400, --violet-600 and the first cut of this surface — sits at
     253-258. Forty degrees apart is not a nuance, it is a different purple,
     and the eye reads it as one thing next to the badge and another thing
     everywhere else.

     This family went first, on its own, on the morning of 2026-09-07 — the
     gutters being the room the page stands in and the cheapest place to prove
     the hue. By that afternoon the owner had asked for the rest ("change all
     the colours to look more like these"), so the exception is gone and the
     whole palette is rotated to the badge; see the Ground & surfaces block at
     the top of this file. NOTHING HERE CHANGED when that happened, which is
     the point: these values were already the badge's, and the rest of the room
     came to them.

     SATURATION MOVED WITH THE HUE, and it had to. Rotating alone — keeping the
     old S — turns the pale end into hot pink (#EDB3F2), because the theme's
     violets run S 0.47-0.71 and the badge runs 0.20-0.33. Lightness is
     untouched throughout: the value structure is what makes the fog readable
     and it was tuned separately. */
  --mist-1: #2A152B;   /* the air out at the screen edge */
  --mist-2: #1D0E1D;   /* mid gutter */
  --mist-3: #0D050E;   /* and against the content column */

  /* An eddy, pale and dark. BOTH ARE NEEDED: fog blocks light as well as
     carrying it, and a bank of nothing but pale turns has no depth in it —
     the dark ones are what the pale ones pass behind. */
  --mist-pale:   #E3C1E4;
  --mist-pale-2: #C38EC5;
  --mist-pale-3: #9E5EA1;
  --mist-dark:   #0D050E;
  --mist-dark-2: #170A17;

  /* The wisps: the only shapes in the surface with an edge you could point at,
     which is why --mist-wisps ships at 0. */
  --mist-wisp-1: #E9D3EA;
  --mist-wisp-2: #CA9FCB;
  --mist-wisp-3: #B584B7;

  /* Motes. js/ctd-mist.js mixes these down to varied alphas per speck, because
     uniform crisp dots on a dark field read as STARS — a completely different
     picture from dust in the air. */
  --mist-mote-1: #A174A2;
  --mist-mote-2: #8C608E;
  --mist-mote-3: #CFB0D0;

  /* The light in the gutter. Two blooms on two clocks, so it is never evenly
     lit and never lit the same way twice. */
  --mist-bloom-1: #CE8CD0;
  --mist-bloom-2: #B36AB6;

  /* The dials, all 0..1. Read by the generator; see its header for what each
     one buys. Wisps at 0 by default — turn them up only if the gutter needs
     something to happen in it, and check the copy still holds the eye. */
  --mist-fog:   0.85;
  --mist-swirl: 1;
  --mist-motes: 0.6;
  --mist-wisps: 0;
  --mist-glow:  0.55;

  /* The resting state, and the reason this surface ships no static fallback
     tile the way the other five do: with JS off the gutter is this gradient,
     which is a perfectly good gutter. */
  --fx-mist-ground: linear-gradient(90deg,
      var(--mist-1) 0%, var(--mist-2) 46%, var(--mist-3) 100%);

  /* The dark the copy sits against. The mask below pulls the whole layer back
     toward the column; this darkens what is left of it, so the near edge of
     the gutter is always the quietest part of it. */
  --mist-shade: color-mix(in srgb, var(--mist-3) 72%, transparent);
  --fx-mist-edge: linear-gradient(90deg, transparent 34%, var(--mist-shade) 100%);

  /* Masked so the air dissolves before it reaches the content column: a hard
     vertical edge beside a paragraph reads as a border. Mirrored per side. */
  --fx-mist-gutter-l: linear-gradient(90deg, black 0%, black 64%, transparent 100%);
  --fx-mist-gutter-r: linear-gradient(270deg, black 0%, black 64%, transparent 100%);

  /* ---- Steel: the gutter girders ----
     A rivetted plate girder standing down the inner edge of each gutter wall,
     so the content column runs between two steel members instead of trailing
     off into stone. Dark and weathered — a member that has been in this room
     as long as the wall has.

     Gradient layers, no svg, and that is a decision rather than a shortcut: a
     girder is a MANUFACTURED object, so unlike the stone it is ALLOWED to be
     perfectly regular. Rivets on a real plate girder are identical and evenly
     pitched; the only thing that must not repeat is the WEAR. So the rivet
     pitch and the wear periods are mutually prime (38 / 89 / 149 / 197 / 313)
     and the stack never comes back into phase inside a viewport.

     HARD STOPS, not ramps. The first pass shaded the plate as one smooth ramp
     and domed the rivets with soft rings; it came out a lilac plastic strip
     with pearls on it. Steel here is FACETED the way the stone is — flat bands
     with a crisp boundary, a bright rolled edge, a dark shadow edge — which is
     the same cel/animation-art argument wall.mjs settled.

     Read the retirement note in DESIGN-SYSTEM.md before touching the rivets:
     `.ctd-seam` was killed because at ornament size a lit dot reads as a little
     bone. What makes these hardware is scale plus form — a 9px dome with a lit
     crown, a dark turned-away rim and a seat it is driven into, never a dot. */
  --girder-w:     var(--s-10);   /* 40px: the gutter's whole width at the 1280
                                    breakpoint, and where the floodlight rail
                                    already runs — so the rail lands on the
                                    girder's outer flange and the comet rides
                                    the steel instead of hanging beside it. */
  /* The rivets. Everything about a dome is derived from --girder-rivet-r, so
     "bigger" is one number and the glint, the outline and the seat cannot
     drift out of proportion with the dome the way four hand-tuned literals
     did. Grown from 4.4px and re-pitched from 38px at the owner's call: on a
     40px web the dome is now about a third of the plate and the run reads as
     widely spaced hardware rather than a bolt seam. */
  --girder-rivet-r: 7px;         /* the dome, so 14px across */
  --girder-pitch: 96px;          /* rivet spacing, about seven diameters */

  /* The plate, in five flat bands from the lit edge to the shadowed one.
     THE PLATE IS PEWTER (2026-09-14, owner: "too woody — more metal/stone").

     This is the third ruling on the web's alloy and it settles the argument
     the previous note left open, so that note is kept here in full: the plate
     was brass, went steel for an hour on "more silvers on the sides", and went
     back to brass on "haver the brass but mix up the studs to contrast a bit
     better". What the brass version could never fix is written below, and it
     is what "too woody" is describing.

     THE PRICE OF BRASS WAS THE WHOLE PROBLEM. --girder-mid and --girder-shade
     take 58% of the plate's DEPTH, so most of any girder is its shadow bands.
     Brass's shadow bands are rgb(78,49,38) and rgb(64,38,35) — those are
     TIMBER colours, and a 40px web of them down both sides of every page is
     the largest single area on the site. Bright gold only ever existed in the
     top 11%. Brass gives gold wherever it is small and brown wherever it is
     big, and the frame is the biggest thing there is.

     PEWTER AT THE SAME VALUES, so nothing below has to be retuned. The ladder
     is matched band for band against the brass it replaces —
       lit   150,141,152  was 148,111,80
       face  112,98,113   was 104,67,49
       mid   76,59,78     was 78,49,38
       shade 58,42,60     was 64,38,35
     — which is why the rivets, the rust periods, the mill grain, the cast
     shadows and every --fx-girder-* layer still read exactly as they were
     tuned. Only the hue moves, from orange to the badge's cool violet-grey.

     IT IS THE BADGE'S OWN SECOND METAL, not a new colour: --pewter is the
     pewter of brass + pewter + purple enamel (see --iron-* above), so the
     frame now belongs to the same three-metal story as the logo instead of
     being a fourth thing.

     THE HARDWARE STAYS BRASS AND THAT IS NOW THE POINT. The studs were already
     three cool alloys and one lifted brass; against a cool web the brass one
     is the bimetal contrast rather than a bolt hiding in its own plate. Do not
     darken it back — at gold-600 it sat 8 points of lightness off the old face
     and disappeared, which is what "the bolts are the same colour" described. */
  --girder-lit:   color-mix(in srgb, var(--pewter) 58%, white);
  --girder-face:  color-mix(in srgb, var(--pewter) 80%, white);
  --girder-mid:   var(--pewter);
  --girder-shade: color-mix(in srgb, var(--pewter) 66%, var(--ink-950));
  --girder-lo:    var(--ink-950);

  /* The other two plate ramps and the butt seam that went with them lived here
     for an hour on 2026-09-07: the run was spliced into panels of steel, brass
     and painted steel to answer "a mix of the 2". They are gone with the
     splice — see the note above BANDS in js/ctd-steel.js for why a panel on a
     40px web reads as a stripe, and where the variance went instead. Leaving
     four unused ramps declared is the loose-end pattern this file warns about
     everywhere else. */
  /* --girder-strap RETIRED 2026-09-14. It was the cross member's own plate
     ramp, then the cross members took the web's ramp and it was left with one
     consumer — --plate-face, the nav rack, which is not a girder part at all.
     A token named for a piece of the frame that only the header spends is the
     loose end the note above describes, so --plate-face now stands on its own
     and this name is gone rather than left pointing somewhere it does not
     belong. Nothing else referenced it. */

  /* A dome catches more light than the plate it is driven into, which is the
     only reason a rivet is visible at all on a member this dark. Three tones,
     because one flat disc is a hole and two is a bevel. */
  /* LIFTED off gold-500 rather than gold-600 (2026-09-07). A brass stud in a
     brass plate is only visible by value, and at gold-600 it was +8 points of
     lightness against the face — a bump, not a bolt. At gold-500 it is +17 and
     reads as hardware sitting proud of the metal it is driven through. */
  --girder-rivet:    color-mix(in srgb, var(--gold-500) 66%, var(--ink-800));
  --girder-rivet-hi: color-mix(in srgb, var(--gold-300) 72%, var(--ink-700));
  --girder-rivet-lo: color-mix(in srgb, var(--gold-600) 26%, var(--ink-950));
  --girder-spec:     var(--bone-100);
  /* The web's rolled top edge: the plate's own metal turned into the light, so
     brighter than --girder-lit but the SAME alloy. It was --girder-rivet-hi
     while the frame and its hardware were one metal; on a steel web that is a
     brass wire laid along the edge. */
  --girder-edge-lit: color-mix(in srgb, var(--girder-lit) 62%, white);

  /* ---- Three alloys for the heads (2026-09-07, owner: "the bolts are the
     same colour") ----
     Every head on the run was --girder-rivet, so a bay of eight was eight of
     the same bolt in eight conditions. A rivet is DRIVEN and then replaced,
     one at a time, over fifty years — the run should read as mismatched
     hardware, not as one part stamped eight times. js/ctd-steel.js picks one
     of these per head, independently of the head's condition.

     The alloy also decides WHAT WEEPS OUT OF IT, which is the whole reason to
     spend three palettes on something 14px across: brass and bronze go GREEN,
     ferrous metal goes ORANGE. One run showing both is a run that has been
     repaired. Getting that backwards is the tell that it was decorated rather
     than built. */
  --rivet-brass:     var(--girder-rivet);
  --rivet-brass-hi:  var(--girder-rivet-hi);
  --rivet-brass-lo:  var(--girder-rivet-lo);
  /* Bright steel: a recent replacement, still clean. The one that catches the
     eye, so it is also the rarest. */
  --rivet-steel:     color-mix(in srgb, color-mix(in srgb, var(--pewter) 64%, white) 82%, var(--ink-700));
  --rivet-steel-hi:  color-mix(in srgb, var(--pewter) 30%, white);
  --rivet-steel-lo:  color-mix(in srgb, var(--pewter) 62%, black);
  /* Black iron: older than the plate, painted once, never again. */
  --rivet-iron:      color-mix(in srgb, var(--pewter) 74%, var(--ink-950));
  --rivet-iron-hi:   color-mix(in srgb, var(--pewter) 66%, var(--bone-500));
  --rivet-iron-lo:   var(--ink-950);
  /* Nickel-plated, and the one that actually catches the eye: 38 points of
     lightness and 53 degrees of hue off the brass plate. Kept the rarest of
     the four — a run of these would read as a row of lights rather than as
     hardware, which is the failure --seam-rivet-r's note describes from the
     other direction. */
  --rivet-nickel:    color-mix(in srgb, color-mix(in srgb, var(--pewter) 46%, white) 86%, var(--bone-300));
  --rivet-nickel-hi: color-mix(in srgb, color-mix(in srgb, var(--pewter) 26%, white) 80%, var(--bone-300));
  --rivet-nickel-lo: color-mix(in srgb, var(--pewter) 70%, var(--ink-900));

  /* What runs out of a head and down the plate. The ALLOY decides which:
     brass and bronze weep green, ferrous metal weeps orange, and a run showing
     both is a run that has been repaired one bolt at a time. Weaker than the
     rust already on this plate — a stain leaving a 14px head is a trickle, and
     at the alpha the crust is painted at it came out as a green tongue. */
  --girder-verd:    color-mix(in srgb, var(--verd-deep) 34%, transparent);
  --girder-verd-2:  color-mix(in srgb, var(--verd-deep) 20%, transparent);
  --girder-verd-3:  color-mix(in srgb, var(--verd-crust) 26%, transparent);

  /* Iron oxide, mixed from the two warm tokens already in the room rather than
     from a new hue — the same argument --stone-4 makes. */
  --girder-oxide:   color-mix(in srgb,
      color-mix(in srgb, var(--err) 40%, var(--gold-600)) 62%, var(--ink-950));
  --girder-rust:    color-mix(in srgb, var(--girder-oxide) 30%, transparent);
  --girder-rust-2:  color-mix(in srgb, var(--girder-oxide) 20%, transparent);
  --girder-rust-3:  color-mix(in srgb, var(--girder-oxide) 14%, transparent);
  /* Crust ON a head, rather than a stain across the plate. js/ctd-steel.js
     batches its marks by colour and emits one element per bucket, so an
     `opacity` attribute would split every bucket it touched — an alpha that
     belongs to a mark has to be in the mark's own token. */
  --girder-crust:   color-mix(in srgb, var(--girder-oxide) 55%, transparent);
  /* The hole a sheared rivet leaves. Below the plate's own darkest band, or it
     reads as one more shadow on the face rather than as a way through it —
     the same argument --wood-void makes about a missing board. */
  --girder-void:    color-mix(in srgb, var(--ink-950) 72%, black);
  /* The shadow a rivet head throws onto the plate, and the lit lip of a scrape
     or a crater. Both are marks js/ctd-steel.js draws as HARD shapes where
     --fx-girder faded them, so both carry a weaker alpha than the tone they
     are made of: a hard disc of --girder-grime round every head is a black
     donut, and a 1px run of --girder-rivet-hi is a pen line drawn on the
     steel rather than metal showing through the dirt. */
  --girder-cast:    color-mix(in srgb, var(--ink-950) 26%, transparent);
  --girder-burr:    color-mix(in srgb, var(--bone-100) 30%, transparent);
  --girder-grime:   color-mix(in srgb, var(--ink-950) 46%, transparent);
  --girder-grain:   color-mix(in srgb, var(--ink-950) 24%, transparent);
  --girder-grain-2: color-mix(in srgb, var(--bone-100) 6%, transparent);

  /* The lit flange, the shadowed one, and the shadow the member throws on what
     is behind it. Light comes from the left in this room, so BOTH girders are
     lit on their left edge and both cast to the right — mirroring the pair
     would light the right-hand one against its own stone. */
  --fx-girder-edge:
      inset 1px 0 0 var(--girder-rivet-hi),
      inset -1px 0 0 var(--girder-lo),
      5px 0 14px -7px var(--girder-lo);

  /* One `background` shorthand, painted top layer first. */
  --fx-girder:
      /* the specular glint, up and left of the dome's crown */
      radial-gradient(circle calc(var(--girder-rivet-r) * 0.26)
            at calc(50% - var(--girder-rivet-r) * 0.38)
               calc(50% - var(--girder-rivet-r) * 0.42),
          var(--girder-spec) 0 78%, transparent 79%)
        50% 0 / var(--girder-w) var(--girder-pitch) repeat-y,
      /* the dome: lit crown, body, and the rim turned away from the light */
      radial-gradient(circle var(--girder-rivet-r)
            at calc(50% - var(--girder-rivet-r) * 0.16)
               calc(50% - var(--girder-rivet-r) * 0.18),
          var(--girder-rivet-hi) 0 30%,
          var(--girder-rivet) 31% 66%,
          var(--girder-rivet-lo) 67% 100%, transparent 100%)
        50% 0 / var(--girder-w) var(--girder-pitch) repeat-y,
      /* its outline, so the dome is seated in the plate and not floating on it */
      radial-gradient(circle calc(var(--girder-rivet-r) * 1.16) at 50% 50%,
          var(--girder-lo) 0 96%, transparent 97%)
        50% 0 / var(--girder-w) var(--girder-pitch) repeat-y,
      /* and the shadow it throws, down and to the right */
      radial-gradient(circle calc(var(--girder-rivet-r) * 1.45)
            at calc(50% + var(--girder-rivet-r) * 0.32)
               calc(50% + var(--girder-rivet-r) * 0.36),
          var(--girder-grime) 0 72%, transparent 92%)
        50% 0 / var(--girder-w) var(--girder-pitch) repeat-y,
      /* Rust, on three periods that agree neither with the rivets nor with each
         other, so no patch of it is ever the same twice down a viewport. Tall
         and narrow, because rust WEEPS — the first pass used broad circles at
         a third more alpha and they read as glowing tangerine blooms rather
         than as a stain. The oxide is mixed back toward the ground for the
         same reason: on a member this dark, rust is a dark brown. */
      radial-gradient(30% 44px at 24% 40%, var(--girder-rust) 0 18%, transparent 76%)
        50% 0 / 100% 197px repeat-y,
      radial-gradient(46% 30px at 72% 62%, var(--girder-rust-2) 0 16%, transparent 70%)
        50% 0 / 100% 313px repeat-y,
      radial-gradient(22% 58px at 54% 24%, var(--girder-rust-3) 0 14%, transparent 82%)
        50% 0 / 100% 149px repeat-y,
      /* weathering along the length — a plate is never evenly dirty */
      repeating-linear-gradient(180deg,
          transparent 0 26px, var(--girder-grime) 44px 62px, transparent 89px)
        repeat,
      /* mill grain across the web. Two periods, or the web reads as corduroy. */
      repeating-linear-gradient(90deg,
          transparent 0 2px, var(--girder-grain) 2px 3px,
          transparent 3px 5px, var(--girder-grain-2) 5px 6px,
          transparent 6px 9px)
        repeat,
      repeating-linear-gradient(90deg,
          transparent 0 5px, var(--girder-grain) 5px 6px, transparent 6px 13px)
        repeat,
      /* the plate itself: five flat bands, lit from the left like everything
         else in this room, with the shadowed edge against the content column */
      linear-gradient(90deg,
          var(--girder-lit) 0 11%,
          var(--girder-face) 11% 34%,
          var(--girder-mid) 34% 72%,
          var(--girder-shade) 72% 92%,
          var(--girder-lo) 92% 100%)
        0 0 / 100% 100% no-repeat;

  /* The cross members: the same girder, laid flat. One runs at every boundary
     between one section and the next, so the run of steel is divided into bays
     that line up with what you are reading. It is --girder-w deep, with the
     same domes at the same pitch along it — the owner's word was "a matching
     bar like the sides, but horizontal", so every number in --fx-girder-t is
     the vertical member's with its axes swapped and nothing is retuned.

     IT STOPS AT THE GIRDERS (2026-09-05, owner: "the bars should only hit the
     edge of the other bar, so they look like squares"). A member used to run
     the whole width and lap OVER the girders it crossed, which made the frame
     one long ladder laid across the page. It now ends on each girder's inner
     face, so the two verticals and two horizontals close a rectangular bay,
     and the crossing is a T rather than an X — which is what gives the bronze
     joint (below) something to strap.

     --girder-pad is unrelated to the cross member: it is the clearance the
     VERTICAL girders need, so the copy is not set hard against the steel. */
  --girder-pad:   var(--s-8);   /* content clearance either side of the measure */
  --girder-t-h:   var(--girder-w);  /* the cross member's depth IS the girder's width */

  /* THE GUTTER, as one number. Everything that must stop at the frame rather
     than run off the screen — the cross member, a section's boarding, an alt
     section's ground — insets by this, so "how wide is the gutter" is asked
     once instead of five times. It measures from the viewport edge to the
     girder's INNER face, which is where the inside of the frame begins, so a
     bay's interior is exactly --wrap across.

     0 below the girder gate, because there are no verticals down there for a
     member to stop against and no room for a gutter worth showing — the same
     two-gates bargain the frame already makes. Overridden in the min-width
     block at the end of this file.

     A PERCENTAGE IN A CUSTOM PROPERTY resolves where it is USED, not where it
     is declared, so every consumer must be a full-width box: the frame wrapper
     (inset on body), a section, or a layer inset:0 inside one. That holds for
     all three consumers today. */
  --bay-gutter: 0px;

  /* Lit along the top, shadowed along the bottom, casting downward: the light
     in this room comes from above as well as from the left. */
  --fx-girder-t-edge:
      inset 0 1px 0 var(--girder-rivet-hi),
      inset 0 -1px 0 var(--girder-lo),
      0 6px 15px -6px var(--girder-lo);

  /* The same two casts, as FILTERS, for the generated frame. js/ctd-steel.js
     chews the plate's long edges, so a box-shadow would throw a crisp
     rectangle off a silhouette that is not one; drop-shadow follows the alpha
     and runs into every nick with the metal.

     drop-shadow has no spread and its blur is the shadow's own, where a
     box-shadow's `-7px` spread was pulling a 14px blur back under the bar —
     so these are shorter and softer than the pair above rather than the same
     numbers restated, and the colour carries its own alpha because there is no
     spread to hide behind. */
  --fx-steel-cast-v: drop-shadow(4px 0 5px color-mix(in srgb, var(--girder-lo) 78%, transparent));
  --fx-steel-cast-h: drop-shadow(0 4px 5px color-mix(in srgb, var(--girder-lo) 78%, transparent));

  /* --fx-girder turned a quarter. The rivet tile is a pitch wide and the
     member deep, placed at 50% so a dome sits on the page's centre line and
     the run is symmetric about it; the rust patches keep their three periods
     with their radii swapped so they still weep DOWN the face; the grime and
     the mill grain run along the length; and the plate is the same five bands,
     lit from the top. */
  --fx-girder-t:
      /* the specular glint, up and left of the dome's crown */
      radial-gradient(circle calc(var(--girder-rivet-r) * 0.26)
            at calc(50% - var(--girder-rivet-r) * 0.38)
               calc(50% - var(--girder-rivet-r) * 0.42),
          var(--girder-spec) 0 78%, transparent 79%)
        50% 50% / var(--girder-pitch) var(--girder-t-h) repeat-x,
      /* the dome: lit crown, body, and the rim turned away from the light */
      radial-gradient(circle var(--girder-rivet-r)
            at calc(50% - var(--girder-rivet-r) * 0.16)
               calc(50% - var(--girder-rivet-r) * 0.18),
          var(--girder-rivet-hi) 0 30%,
          var(--girder-rivet) 31% 66%,
          var(--girder-rivet-lo) 67% 100%, transparent 100%)
        50% 50% / var(--girder-pitch) var(--girder-t-h) repeat-x,
      /* its outline, so the dome is seated in the plate and not floating on it */
      radial-gradient(circle calc(var(--girder-rivet-r) * 1.16) at 50% 50%,
          var(--girder-lo) 0 96%, transparent 97%)
        50% 50% / var(--girder-pitch) var(--girder-t-h) repeat-x,
      /* and the shadow it throws, down and to the right */
      radial-gradient(circle calc(var(--girder-rivet-r) * 1.45)
            at calc(50% + var(--girder-rivet-r) * 0.32)
               calc(50% + var(--girder-rivet-r) * 0.36),
          var(--girder-grime) 0 72%, transparent 92%)
        50% 50% / var(--girder-pitch) var(--girder-t-h) repeat-x,
      /* rust, on the girder's three coprime periods, tiled along the member */
      radial-gradient(44px 30% at 40% 24%, var(--girder-rust) 0 18%, transparent 76%)
        50% 0 / 197px 100% repeat-x,
      radial-gradient(30px 46% at 62% 72%, var(--girder-rust-2) 0 16%, transparent 70%)
        50% 0 / 313px 100% repeat-x,
      radial-gradient(58px 22% at 24% 54%, var(--girder-rust-3) 0 14%, transparent 82%)
        50% 0 / 149px 100% repeat-x,
      /* weathering along the length */
      repeating-linear-gradient(90deg,
          transparent 0 26px, var(--girder-grime) 44px 62px, transparent 89px)
        repeat,
      /* mill grain along the member, two periods */
      repeating-linear-gradient(180deg,
          transparent 0 2px, var(--girder-grain) 2px 3px,
          transparent 3px 5px, var(--girder-grain-2) 5px 6px,
          transparent 6px 9px)
        repeat,
      repeating-linear-gradient(180deg,
          transparent 0 5px, var(--girder-grain) 5px 6px, transparent 6px 13px)
        repeat,
      /* the plate: five flat bands, lit from above */
      linear-gradient(180deg,
          var(--girder-lit) 0 11%,
          var(--girder-face) 11% 34%,
          var(--girder-mid) 34% 72%,
          var(--girder-shade) 72% 92%,
          var(--girder-lo) 92% 100%)
        0 0 / 100% 100% no-repeat;


  /* ============================================================
     THE PRINTED PART — the resin's own colour, the seven category
     pigments, and the effect primitives for the builder's controls.
     2026-09-07.

     WHY THESE THREE RUNS ARE HERE AND THE REST OF THE MATERIAL IS NOT.
     The parts' whole material lives in css/v3/parts.css, enqueued only on
     builder pages because that is the one surface that draws them. What
     cannot live there is anything this file is the sole legal home for:

       --resin    a raw hex. Every stated colour in v3 is in this file
                  (see --slab for the same case) and nowhere else.
       --pig-*    one raw hex per category, same rule.
       --fx-*     effect primitives. tools/contract/css-tokens.mjs lets a
                  drop-shadow, gradient or text-shadow be SPELLED OUT only
                  inside an --fx-* declaration in tokens.css; every other
                  stylesheet may only spend one as a var(). That is the
                  effect budget with teeth, and it is why these are here
                  rather than beside the rules that use them.

     Everything derived from them — the value ladder, the tag's dome, the
     base's paint zones, the animations — is in parts.css, which is also
     where the long design notes for each live.
     ============================================================ */

  /* ---- the resin -------------------------------------------
     A COOL, CLEAN NEAR-BLACK: the colour of fresh printed plastic. It is a
     raw hex rather than a mix for exactly the same reason --slab is one —
     the theme's neutrals are the palette of a hundred-year-old stadium, and
     mixed together they make mud. The parts are meant to be the one NEW
     thing standing in an old room, and a new thing is not the old room's
     colours at a different brightness.

     MATT BLACK IS THE OWNER'S CALL, three times over (2026-09-07: "it would
     be matt black like the rest of the sprue's resin ... but do give it some
     definition"). A black object is not read by its field, it is read by its
     EDGES — see assets/photos/devious_rainbow_sprue, where frame, runner,
     tags and lettering are all one near-black plastic and every part of it
     is legible. Lifting the field to make the part visible gets you a grey
     object, and a grey object with a gradient on it looks polished. All of
     the modelling is therefore in parts.css as bone/ink alphas over this
     one flat, which is also what lets the base repaint without any of the
     shading being re-picked. */
  --resin: #2E3138;

  /* ---- the categories, as PIGMENT rather than as text ----
     THE --cat-* TOKENS ABOVE ARE TEXT COLOURS: pale, low-chroma, tuned to
     read as type on near-black. Mixed a third of the way into a dark base
     they produce seven greys with a rumour of hue in them. A moulded chip
     is not type — it is pigment THROUGH the plastic — so these are stated
     saturated colours, taken down into the resin in parts.css far enough
     that the pale raised code still has a ratio to sit on.

     JEWEL TONES, not poster paint (owner, 2026-09-07: refined, medieval,
     dark). Stained glass is the one place a medieval building keeps
     saturated colour and it keeps it exactly this way — deep and dense,
     lit from behind rather than pale. Every category holds its identity
     from --cat-*: sapphire general, emerald agility, oxblood strength,
     amber passing, amethyst mutation, slate devious, ivory trait, garnet
     characteristic.

     THE EIGHTH WAS FREE, WHICH IT WOULD NOT HAVE BEEN A MONTH EARLIER. While
     the pigment was believed to be a moulding colour, adding one meant asking
     the moulder for a resin he does not stock — which is why the note in
     js/builder-v2/ui/skill-display.js said for weeks that seven was the
     ceiling. The 2026-09-07 repaint settled that the product is ONE black and
     the pigment is hue seen through it, so the ceiling was on the drawing, not
     on the part. Characteristic (owner, 2026-09-16) is the first category
     added since, and garnet is the rose slot --cat-c takes for the same
     reason: it is the widest remaining gap in the wheel.

     NOTHING THAT PRINTS A SKILL NAME MAY REACH FOR THESE. --cat-* is the
     text ladder; these are the moulding's. */
  --pig-g: #3A6ABA;   /* General   · sapphire */
  --pig-a: #2C8E56;   /* Agility   · emerald  */
  --pig-s: #B2472C;   /* Strength  · oxblood  */
  --pig-p: #BC861C;   /* Passing   · amber    */
  --pig-m: #7A3FB4;   /* Mutation  · amethyst */
  --pig-d: #52607E;   /* Devious   · slate — cooled, or it reads as the base */
  --pig-t: #AE9F84;   /* Trait     · ivory    */
  --pig-c: #A83A6B;   /* Character.· garnet   */

  /* ---- the builder's controls: the lit layer's primitives ----
     THE BRIEF WAS "go ham on glows and animation, but don't make it
     garish", and the rule those resolve to is: LIGHT THE THING YOU ARE
     AIMING AT, everything else stays dark. On any one screen that is the
     primary action and the gauge cell you just filled — two lit objects,
     not twenty. Four constraints keep it off the garish side: no hue ever
     changes, rest motion is slow and small (7s+, under 10%), response
     motion is fast and once (600ms and out), and every glow is a colour the
     control already is.

     This does not reopen the builder's half effect budget. A button you are
     about to press IS "the chosen thing"; nothing below lifts, zooms or
     rakes. The RULES that spend these — which control, on what state — are
     in css/v3/parts.css. */

  /* the button: three strengths of one brass. The gap between them is what
     makes hover read as a response rather than a state change, and --hot is
     only ever :hover or :focus. */
  --fx-btn-glow:  0 0 0 1px color-mix(in srgb, var(--gold-500) 30%, transparent),
                  0 4px 20px -8px color-mix(in srgb, var(--gold-500) 42%, transparent);
  --fx-btn-hot:   0 0 0 1px color-mix(in srgb, var(--gold-300) 58%, transparent),
                  0 6px 28px -6px color-mix(in srgb, var(--gold-500) 62%, transparent);
  --fx-btn-press: 0 0 0 1px color-mix(in srgb, var(--gold-600) 50%, transparent),
                  0 1px 7px -4px color-mix(in srgb, var(--gold-500) 38%, transparent);
  /* iron and resin get a rim on hover and no glow — they are not the thing
     you are aiming at */
  --fx-btn-quiet: 0 0 0 1px color-mix(in srgb, var(--bone-100) 20%, transparent);

  /* the travelling highlight, used as a FILL inside the svg */
  --fx-sheen-ink: color-mix(in srgb, var(--bone-100) 26%, transparent);
  --fx-scan-ink:  color-mix(in srgb, var(--bone-100) 8%, transparent);

  /* the gauge. The bloom sits on the filled cells only, so an empty gauge is
     completely dark and a full one is lit along its length. --tint is set by
     the gauge's own is-* class in parts.css. */
  --fx-gauge-glow: drop-shadow(0 0 9px color-mix(in srgb, var(--tint) 55%, transparent));
  --fx-gauge-hot:  drop-shadow(0 0 17px color-mix(in srgb, var(--tint) 85%, transparent));

  /* the switch — one glow, because it is a state you set and then stop
     looking at. It had no name until 2026-09-07 and was written out inline
     at its one call site, which is exactly how an effect escapes a budget:
     nothing that is not a token can be counted. */
  --fx-toggle-glow: drop-shadow(0 0 8px color-mix(in srgb, var(--gold-500) 44%, transparent));

  /* timings. Rest slow, response fast. --t-drip borrows the frame's own
     trickle so a weeping button and a weeping girder run at the same pace —
     they are the same weather. */
  --t-sheen:   600ms;
  --t-wipe:    520ms;
  --t-scan:    9.5s;
  --t-btn-breathe: 7.5s;
  --t-pip:     2.6s;
  --t-drip:    var(--t-trickle, 11s);

  /* ---- Bronze: the T joint ----
     Where a cross member butts into a girder there is a corner, and this is
     the fitting over it: a bronze tee strap — a vertical band lying along the
     column with one arm reaching out along the beam — with a washered bolt
     through the band (2026-09-05, owner: "a bronze T join for where they meet,
     with its own Bolt attached", then "the joins are quite chunky, and not
     like a t shape, and the colour doesnt quite go with the existing one
     there").

     IT IS A TEE, AND IT IS FLUSH. The first pass was a rectangular gusset — a
     slab bolted to the frame. The second cut the letter but drew both strokes
     NARROWER than the steel under them, so the fitting sat on the frame like a
     sticker with girder showing either side of it. The owner's correction was
     the whole answer: "they should basically be the same width as the bars they
     are on top of, like a proper joining brace." --joint-stem IS --girder-w and
     --joint-arm IS --girder-t-h, so the band's edges are the girder's edges and
     the arm's edges are the member's, and the steel simply becomes bronze at
     the corner. Nothing of the frame is left peeping out beside it.

     WHAT IS VISIBLE IS THE REACH, then — not the strokes' width. --joint-reach
     is how far the brace runs past the crossing along each member, and it is
     the only number that decides how big the fitting looks.

     THE FITTING IS THE FRAME, POLISHED. This block used to read "every tone
     is the steel's own, warmed", and the argument still stands — a fitting
     mixed straight from the golds had nothing tying it to what it is bolted
     to. What changed on 2026-09-07 is the frame: the girder is brass now, so
     warming it further would put brass on brass and lose the fitting.

     So the mix is the same idea one step further along the SAME ramp. Each
     tone is still the girder tone it sits against, taken toward the lit end of
     the gold rather than toward gold from grey: dull cast brass for the frame,
     polished brass for the thing bolted to it. They cannot drift apart,
     because they are still made of each other — and the fitting stays legible,
     because polish is a value step and not a hue change.

     Every number derives from --girder-w and --girder-rivet-r, so the joint
     cannot drift out of scale either. */
  --joint-reach:   calc(var(--girder-rivet-r) * 2.4); /* past the crossing */
  --joint-stem:    var(--girder-w);                /* the band IS the girder */
  --joint-arm:     var(--girder-t-h);              /* the arm IS the member */
  --joint-arm-w:   calc(var(--joint-reach) * 1.5); /* out along the member */
  --joint-w:       calc(var(--joint-stem) + var(--joint-arm-w));
  --joint-h:       calc(var(--joint-arm) + var(--joint-reach) * 2);
  --joint-chamfer: calc(var(--girder-rivet-r) * 0.8);
  --joint-bolt-r:  calc(var(--girder-rivet-r) * 1.35); /* the band is 40px now */

  /* ONE FLAT FACE AND A BEVEL, which is .ctd-plate's construction rather than
     the girder's. The girder and the member are shaded in five and four flat
     bands because they are ROLLED SECTIONS seen end-on — the bands are the
     flange curving away. A tee strap is a flat plate seen face-on; banding it
     across 68px gave three stacked rectangles that read as a layered cake, so
     the relief here comes from the edge bevel and the wear, as it does on the
     nameplate. */
  --joint-lit:   color-mix(in srgb, var(--gold-300) 72%, var(--girder-lit));
  --joint-face:  color-mix(in srgb, var(--gold-500) 66%, var(--girder-face));
  --joint-lo:    color-mix(in srgb, var(--gold-600) 34%, var(--ink-950));
  --joint-spec:  color-mix(in srgb, var(--bone-100) 52%, var(--gold-300));

  /* The bolt, and the one thing that must NOT follow the rest of the fitting
     toward the steel: a VALUE LADDER against --joint-face. Warming every tone
     the same way flattened them all onto the plate — the rim stopped being
     darker than the metal it is driven into and the washer stopped being
     lighter — so what was left was flat concentric rings with a catchlight in
     them, which is an eye. Each of these is placed by where it must sit
     relative to the face, not by which girder tone it came from:

       hi     clearly LIGHTER than the face   (the crown)
       bolt   a step lighter                  (the body)
       lo     clearly DARKER than the face    (the rim turned away) */
  --joint-bolt-hi: color-mix(in srgb, var(--gold-300) 70%, var(--girder-lit));
  --joint-bolt:    color-mix(in srgb, var(--gold-500) 72%, var(--girder-face));
  --joint-bolt-lo: color-mix(in srgb, var(--gold-600) 30%, var(--ink-950));
  /* Verdigris, in the recesses. Taken out in the morning's rotation and put
     back the same afternoon at the owner's call — see the --verd block. It is
     mixed from --verd now rather than from --ok, which is the same "mix it
     from the room" argument --girder-oxide makes about iron oxide, except
     that the room has a real copper green in it at last instead of borrowing
     the semantic one. It only ever appears at this alpha and only where damp
     sits: a bronze fitting with a green FACE is a garden ornament, not a
     bridge fitting. */
  --joint-patina: color-mix(in srgb,
      color-mix(in srgb, var(--verd) 46%, var(--girder-lo)) 22%, transparent);

  /* The chamfer's two steps, the light bouncing back into its shadowed side,
     the sheen across the face and the grime along its bottom edge. Every one
     is --joint-face moved toward a tone the fitting already has, so the bronze
     cannot come apart into a palette of its own.

     THE CHAMFER IS WHAT MAKES IT METAL. --joint-lit / --joint-lo are the cut
     edge; these are the LAND behind it, a step short of the cut both ways, and
     a fitting with both reads as thick plate where one alone reads as a shape
     with a keyline round it. --joint-bounce is the one that is easy to leave
     out and shows: without light coming back off the plate below, the
     shadowed edge is a hole rather than a surface turned away. */
  /* WARMED FROM THE GOLD, not from --joint-lit. The lit land is the biggest
     area of the fitting after the face itself, so it is what decides the
     fitting's HUE — and --joint-lit is a warm grey (it is the girder's own lit
     band with a little gold in it), which took the tee grey-brown and lost the
     bronze the owner asked for in the first place. */
  --joint-hi:     color-mix(in srgb, var(--gold-300) 28%, var(--joint-face));
  --joint-shade:  color-mix(in srgb, var(--joint-lo) 38%, var(--joint-face));
  --joint-bounce: color-mix(in srgb, var(--joint-lit) 26%, var(--joint-shade));
  --joint-sheen:  color-mix(in srgb, var(--joint-spec) 4%, transparent);
  --joint-gleam:  color-mix(in srgb, var(--joint-spec) 15%, transparent);
  --joint-grime:  color-mix(in srgb, var(--ink-950) 24%, transparent);

  /* The bevel, lit along the top and left like everything else in this room.
     Inset ONLY, and there is no cast shadow to go with it: the nameplate gets
     one from a drop-shadow filter because its chamfer is a clip-path and a
     clip-path cuts a box-shadow off with the corners — but the nameplate can
     put the filter on the parent and the clip on a child, and a joint drawn on
     a pseudo-element has no second box to split them across. On one element
     the clip is applied after the filter and eats the shadow. A gusset is
     bolted flush to the steel either side of it, so a strong bottom edge is
     the right amount of relief anyway; the plate it sits on is what casts. */
  --fx-joint-edge:
      inset 0 1px 0 var(--joint-lit),
      inset 1px 0 0 color-mix(in srgb, var(--joint-lit) 55%, transparent),
      inset 0 -2px 0 var(--joint-lo),
      inset -1px 0 0 var(--joint-lo);

  /* The face, top layer first: the bolt (the girder's dome recipe at the same
     fractions of ITS radius, so the two pieces of hardware are the same object
     at two sizes), the washer it is pulled down onto, grime gathering low, and
     the flat bronze.

     THE BOLT RIDES IN A BAND-WIDTH TILE, because the tee is mirrored: the band
     is on the left of the box at the left-hand joint and on the right at the
     right-hand one, and the bolt goes through the BAND. Its layers are sized
     `var(--joint-stem) 100%`, so their `at 50% 50%` is the middle of that tile
     and not of the element, and the rule then moves the whole group with one
     `background-position-x: left | right`. The plate layers are `100% 100%`,
     which leaves them zero positioning slack, so the same declaration passes
     straight through them.

     WHY NOT A `--joint-bolt-x` CUSTOM PROPERTY SET BY EACH PSEUDO: it does not
     work, and the failure is silent. A custom property's own var() references
     are substituted at computed-value time ON THE ELEMENT IT IS DECLARED ON —
     here, html.ctd-v3 — so --joint-bolt-x resolves (to nothing) before any
     pseudo is reached, --fx-joint becomes the guaranteed-invalid value, and
     `background` computes to `none`. Nothing warns; the fitting just vanishes
     and leaves its box-shadow bevel behind. Anything a rule needs to vary
     per-element has to be IN the rule, not inside the token.

     Mirroring with `scaleX(-1)` would have been one polygon instead of two, at
     the cost of lighting the right-hand joint from the right — the mistake the
     girders own edge shadows already document.

     NO MILL GRAIN. The girder and the member both carry it and it is what
     makes them rolled steel — but the same repeating hairlines across 53px of
     WARM plate read as timber, and the first pass came out as a wooden luggage
     tag screwed to the frame. Grain is a steel texture here, not a metal one.

     THE WASHER IS LIT, NOT DARK. It was --joint-shade under a --joint-lo
     outline and the two dark rings merged into one black donut with a pale
     bead in the middle, which reads as an eyelet. A washer is a bright flat
     annulus with a thin shadow under its outer edge, so the ring is now a
     bronze one and only the seat below it is dark. */
  --fx-joint:
      /* The specular, INSIDE the crown rather than beside it. The girder's
         rivet puts its glint at 0.38/0.42 of a 7px radius with a 0.26 radius
         of its own, which lands it on the body band — at 7px the two are two
         pixels apart and read as one hot crown, but on a head half again as
         big they separate into a pale disc with a brighter dot next to it, and
         a disc with a catchlight in it is an EYE. Smaller, and pulled in until
         it sits wholly within the crown: one highlight with a hot centre. */
      radial-gradient(circle calc(var(--joint-bolt-r) * 0.18)
            at calc(50% - var(--joint-bolt-r) * 0.2)
               calc(50% - var(--joint-bolt-r) * 0.24),
          var(--joint-spec) 0 78%, transparent 79%)
        0 0 / var(--joint-stem) 100% no-repeat,
      /* The head: lit crown, body, and the rim turned away from the light —
         the girder rivet's own three bands, near enough, because they are what
         makes a dome read as a dome. What went wrong was never the fractions;
         it was the tones flattening onto the plate. */
      radial-gradient(circle var(--joint-bolt-r)
            at calc(50% - var(--joint-bolt-r) * 0.14)
               calc(50% - var(--joint-bolt-r) * 0.16),
          var(--joint-bolt-hi) 0 34%,
          var(--joint-bolt) 35% 74%,
          var(--joint-bolt-lo) 75% 100%, transparent 100%)
        0 0 / var(--joint-stem) 100% no-repeat,
      /* The seat it is driven into — a hairline of the plate's own shadow just
         proud of the dome, which is the girder rivet's fourth layer and the
         whole reason a dome reads as set INTO metal rather than stuck onto it.

         THERE IS NO WASHER, and getting rid of it is what fixed the bolt. It
         was there to say "bolt, not rivet", and it worked as a label and
         failed as a picture: an annulus outside the head's own dark rim is a
         fourth concentric band, and four rings around a hot dot is an eye, at
         every size and every tone I could find for it. What distinguishes this
         from the domes on the bar either side is that it is bronze, larger,
         and alone in the middle of a fitting — which is enough. */
      radial-gradient(circle calc(var(--joint-bolt-r) * 1.12) at 50% 50%,
          var(--joint-lo) 0 96%, transparent 97%)
        0 0 / var(--joint-stem) 100% no-repeat,
      /* the shadow it throws, down and to the right */
      radial-gradient(circle calc(var(--joint-bolt-r) * 1.42)
            at calc(50% + var(--joint-bolt-r) * 0.3)
               calc(50% + var(--joint-bolt-r) * 0.34),
          var(--girder-grime) 0 74%, transparent 93%)
        0 0 / var(--joint-stem) 100% no-repeat,
      /* Oxide, low and off to one side. It rides in the band's tile with the
         bolt, so it moves to the other side of the fitting when the tee is
         mirrored — which is the only variation the two ends of a member get,
         and enough. The frame's own wear must not repeat because you see 900px
         of it at once; a tee strap is a 57px fitting. */
      radial-gradient(70% 20% at 32% 74%, var(--girder-rust) 0 18%, transparent 76%)
        0 0 / var(--joint-stem) 100% no-repeat,
      /* THE ARM'S OWN BEVEL. --fx-joint-edge is a box-shadow, so it follows
         the BOX and the clip-path then trims it to whatever edges of the tee
         happen to lie on the box — the band's three outer edges and the arm's
         end. The arm's long edges are interior to the box and got nothing, and
         a raised strap with two flat cuts along it reads as a paper tab. These
         two hairlines are those edges. They cross the band as well, which is
         right: a tee strap is pressed from one piece, so the arm's edges DO
         continue behind the band — and the washer covers them there anyway. */
      linear-gradient(180deg,
          transparent 0 calc(50% - var(--joint-arm) / 2),
          var(--joint-lit) calc(50% - var(--joint-arm) / 2)
                           calc(50% - var(--joint-arm) / 2 + 1px),
          transparent calc(50% - var(--joint-arm) / 2 + 1px)
                      calc(50% + var(--joint-arm) / 2 - 1px),
          var(--joint-lo) calc(50% + var(--joint-arm) / 2 - 1px)
                          calc(50% + var(--joint-arm) / 2),
          transparent calc(50% + var(--joint-arm) / 2))
        0 0 / 100% 100% no-repeat,
      /* grime gathering along the bottom edge */
      linear-gradient(180deg, transparent 0 62%, var(--girder-grime) 100%)
        0 0 / 100% 100% no-repeat,
      /* the face */
      linear-gradient(var(--joint-face), var(--joint-face))
        0 0 / 100% 100% no-repeat;

  /* ---- Bronze: the section nameplate ----
     A section's title on a bronze plaque bolted to the cross member above it.

     IT WAS THE SAME STEEL AS THE FRAME, and that is what was wrong with it: a
     broad flat strap of girder face hung on 40px of girder face reads as a
     title painted on the beam rather than announced on a plate (2026-09-05,
     owner: "make the section name plates smaller and more decorative, maybe go
     for a different metal type like the bronze look on the side. or at least
     have a border of sorts around it").

     SO IT IS THE FITTINGS' METAL. Bronze is already what this room spends on
     the parts that JOIN the frame — the tee straps at every bay corner — and a
     nameplate is the other thing bolted to steel. Every tone here is therefore
     a --joint-* token rather than a new family: one bronze in the room, and if
     it ever earns its own --bronze-* names both pieces move together.

     IT IS BOLTED ACROSS THE MEMBER, centred on it (--plate-tuck). Two earlier
     passes hung it: lapped under the bar, then butted up beneath it. Both were
     answers to the same complaint — the title reading low — and the second one
     fixed it by making the plaque and its box the same rectangle. Straddling
     keeps that (the plate paints OVER the steel, so none of it is hidden) and
     adds what the hung versions never had: the plaque is ON the frame, so a
     bay's top edge and its title are one piece of ironwork.

     EVERY NUMBER COMES OFF THE RIVET. --plate-rivet-r sets the hardware; a
     rivet's element is 4r square and parked in the corner, so its centre is 2r
     in from both edges, which IS --plate-inset; and that inset plus the rivet's
     own radius is the rim the border is cut into. One number moves the plaque,
     its frame, its bolts and its clearance together, and the mobile step at the
     end of this file moves --girder-rivet-r, so it moves them there too. */
  --plate-rivet-r: calc(var(--girder-rivet-r) * 0.62); /* smaller than the bar's */
  --plate-inset:   calc(var(--plate-rivet-r) * 2);     /* rivet centre in from each edge */
  --rivet-box:     calc(var(--plate-rivet-r) * 4);     /* one dome's element, seat included */
  --plate-frame:   calc(var(--plate-inset) + var(--plate-rivet-r) + 2px); /* edge to field */
  /* THE ENDS ARE CUT, and cut further across than down: a symmetric 45° corner
     on a plate this size is a border-radius to the eye (the wall's stones say
     the same thing), while a long shallow cut is the end of a nameplate. The
     rivets move in with it — --plate-rivet-x is far enough from the cut line
     that the seat of a dome never crosses it. */
  --plate-cut-x:   calc(var(--plate-rivet-r) * 4);
  --plate-cut-y:   calc(var(--plate-rivet-r) * 1.6);
  --plate-rivet-x:   calc(var(--plate-rivet-r) * 3.4); /* rivet centre in from each END */
  --plate-rivet-off: calc(var(--plate-rivet-x) - var(--rivet-box) / 2); /* its box, to get there */
  --plate-min-w:   260px;  /* so a two-word label is still a plaque */
  /* The tones are the tee strap's, read straight off --joint-*: there is one
     bronze in this room and both fittings are made of it. --plate-face below
     is NOT one of them — it is the steel plate face the wordmark lockups
     spend (tools/brand/wordmark.mjs bakes it into the lockup SVGs). The nav's
     plate rack, its other reader, went with the plates on 2026-09-16.

     AND IT IS ACTUALLY STEEL NOW (2026-09-14, owner: "too woody — more
     metal/stone"). This note has called it "the steel plate face" since it was
     written, while the value underneath it was var(--girder-strap) — brass,
     rgb(104,67,49). With the web gone pewter and the facia gone stone, the
     rack was the last warm thing left in the header and it was reading as four
     wooden blocks bolted to a wall: brown slabs, and --girder-grain ruled
     across them at 3px, which is grain whatever the metal is called.

     It is lifted clear of the stone behind it rather than matched to the web.
     Face rgb(103,87,97) against the facia's rgb(70,67,68) is what makes a
     plate sit ON the wall; against the flange's rgb(112,98,113) it is close
     enough to be the same alloy and far enough not to melt into it.

     VALUE HELD DELIBERATELY. The brass it replaces was luminance 0.088 and
     this is 0.105, so bone-100 labels keep about 5.4:1 — cooling a plate by
     lightening it would have taken the nav's own text down through 4.5:1,
     which is the one thing the rack cannot spend. */
  --plate-face:    color-mix(in srgb, var(--pewter) 72%, var(--bone-500));
  --plate-wash:    color-mix(in srgb, var(--joint-lit) 30%, transparent);
  --plate-sink:    color-mix(in srgb, var(--ink-950) 34%, transparent);
  --plate-sunk:    color-mix(in srgb, var(--ink-950) 13%, transparent);

  /* WHERE THE PLATE SITS: STRADDLING THE MEMBER, centred on it, so the plaque
     reads as part of the frame rather than as a label hung off it (2026-09-05,
     owner: "the section headers on the home page should be on the bar centred,
     so it looks like its integrated to the borders. not just sat under it").

     THIS IS NOT THE OLD LAP COMING BACK. The lap put the plate's TOP THIRD
     BEHIND the bar, so the rectangle you could see was shorter than the
     rectangle the title was centred in and the text read low — the trap the
     owner called out twice. Here the plate paints OVER the steel (z-index 1
     against the frame's 0), so the whole plaque is visible, the rim is the
     same width all the way round it, and the title is central in the only
     rectangle there is. Straddling and lapping look alike on a diagram and are
     opposites on a screen: what matters is which one is in front.

     HEIGHT-FREE, WHICH IS THE WHOLE REASON IT IS A TRANSFORM. Centring on the
     boundary means moving the plate up by half its own height, and CSS cannot
     name that number: --plate-fs is fluid and a long label wraps to two lines
     on a phone, so any arithmetic here would be right at one width and wrong
     at another. A percentage TRANSLATE resolves against the element's own
     border box, so it is exact at every size and every wrap with nothing to
     keep in sync — and it costs the layout nothing, because a transform does
     not move the box it came from.

     THE FLOW BOX STAYS PUT, and --plate-tuck is what stops that showing. The
     untransformed box still occupies its full height below the boundary, so
     the next item in the stack would sit half a plate lower than it looks like
     it should. The tuck takes back half the MEMBER's depth — the part of the
     plate that is over steel rather than over the section — which keeps the
     air under the plaque in frame units instead of in plate units, so a
     two-line title on a phone opens the gap rather than closing it. */
  --plate-tuck: calc(var(--girder-w) / -2);

  /* The title sits in the sunk field, and the field is the box inset by the
     rim, so the padding is rim + breath on every side and the letters are
     central by construction rather than by arithmetic that has to be redone
     whenever the bar changes depth.

     --plate-lift is the optical correction on top of that, and it goes UP.
     Oswald's line box is not symmetric about its caps: at line-height 1.05 the
     baseline sits 0.98em down and the cap band starts 0.72em above it, which
     leaves 0.26em of air over the letters and 0.07em under them. Centring the
     BOX therefore hangs the caps about a tenth of an em low, which is what you
     see rather than what you measure. Half that gap comes off the top padding
     and goes on the bottom, so the plate's height does not change and only the
     letters move — and it is a fraction of --plate-fs, so it tracks the type
     as the plate scales instead of being a pixel tuned at one width. */
  --plate-breath:   var(--s-2);
  --plate-breath-x: var(--s-5);
  --plate-lift:     calc(var(--plate-fs) * 0.1);
  --plate-pad-top:    calc(var(--plate-frame) + var(--plate-breath) - var(--plate-lift));
  --plate-pad-bottom: calc(var(--plate-frame) + var(--plate-breath) + var(--plate-lift));
  --plate-pad-x:      calc(var(--plate-frame) + var(--plate-breath-x));
  /* IT EXPANDS AND CONTRACTS (2026-09-05, owner). Two different sizings, and
     the plaque needs both: the box is fit-content, so it is exactly as wide as
     the label bolted to it and no two sections carry the same plate; and the
     title itself runs fluid between the mobile and desktop steps instead of
     jumping at a breakpoint, which is what stops a 320px screen being handed
     the same slab a 1400px one gets. */
  --plate-fs: clamp(var(--fs-20), 0.85rem + 1.05vw, var(--fs-28));

  /* The bevel is --fx-joint-edge: the same metal in the same light, so the
     plaque and the straps are lit as one fitting. */
  /* The chamfer is a clip-path and a clip-path cuts a box-shadow off with the
     corners, so the cast shadow is a FILTER on the plate, which follows the
     clipped silhouette. Shorter throw than the old slab: a smaller plaque
     stands off the beam by less. */
  --fx-plate-cast: drop-shadow(0 5px 4px color-mix(in srgb, var(--ink-950) 72%, transparent));
  /* Cast lettering, standing a pixel off the plate. Shared with the nav. */
  --fx-plate-stamp:
      0 1px 0 var(--girder-lo),
      0 2px 3px color-mix(in srgb, var(--ink-950) 55%, transparent);

  /* The face, top layer first: oxide weeping from under each top rivet (where
     water sits on a plate bolted to a beam), one broad stain that is under
     nothing in particular, grime along the bottom edge, the light falling
     across the top of a cast face, and the flat bronze.

     NO MILL GRAIN. The girder and the member both carry it and it is what
     makes them rolled steel, but the same repeating hairlines on WARM metal
     read as timber — the trap the tee strap already documents. */
  --fx-plate:
      radial-gradient(calc(var(--plate-rivet-r) * 1.6) 32%
            at var(--plate-inset) 30%,
          var(--girder-rust) 0 20%, transparent 78%),
      radial-gradient(calc(var(--plate-rivet-r) * 1.3) 28%
            at calc(100% - var(--plate-inset)) 26%,
          var(--girder-rust-2) 0 20%, transparent 78%),
      radial-gradient(22% 42% at 68% 78%, var(--girder-rust-3) 0 16%, transparent 80%),
      linear-gradient(180deg, transparent 0 66%, var(--girder-grime) 100%),
      linear-gradient(180deg, var(--plate-wash) 0 5%, transparent 32%),
      linear-gradient(var(--joint-face), var(--joint-face));

  /* THE BORDER: a panel cut into the face, which is the "border of sorts" the
     owner asked for and the reason the plaque can be small and still read as a
     nameplate rather than a label. It is the plate's own bevel REVERSED — dark
     along the top and left, lit along the bottom and right — because that is
     the difference between a shape sunk into metal and one sat on top of it.

     Drawn as four edge gradients rather than a box-shadow ring: the panel is
     inset from a chamfered plate, so a shadow would need a clip of its own,
     while a background layer is simply the strip it covers. */
  --fx-plate-field:
      linear-gradient(180deg, var(--joint-lo) 0 1px, transparent 1px)
        0 0 / 100% 100% no-repeat,
      linear-gradient(90deg, var(--joint-lo) 0 1px, transparent 1px)
        0 0 / 100% 100% no-repeat,
      linear-gradient(0deg, var(--joint-lit) 0 1px, transparent 1px)
        0 0 / 100% 100% no-repeat,
      linear-gradient(270deg, var(--joint-lit) 0 1px, transparent 1px)
        0 0 / 100% 100% no-repeat,
      /* the rim's shadow falling into the top of the field */
      linear-gradient(180deg, var(--plate-sink) 0 8%, transparent 48%)
        0 0 / 100% 100% no-repeat,
      /* and the verdigris that collects in the bottom of it, because a sunken
         field on a bronze plate is a tray. Bottom eighth only — the letters
         sit above it and nothing green may go under a letter.

         THREE UNEQUAL POOLS AND A CRUST, not one band. An even stripe along
         the bottom is the failure this file warns about twice already: spread
         evenly it stops being corrosion and becomes green paint. The crust is
         continuous because the very bottom of a tray always holds water; what
         sits ON it is asymmetric — a long pool left of centre, a shorter one
         near the right end, and a third that wanders in at 62% for no reason
         at all. */
      radial-gradient(34% 46% at 27% 100%, var(--verd-seat) 0 24%, transparent 82%)
        0 0 / 100% 100% no-repeat,
      radial-gradient(21% 34% at 78% 100%, var(--verd-seat) 0 20%, transparent 78%)
        0 0 / 100% 100% no-repeat,
      radial-gradient(13% 26% at 62% 100%, var(--verd-lip) 0 18%, transparent 76%)
        0 0 / 100% 100% no-repeat,
      linear-gradient(0deg, var(--verd-lip) 0 2.5%, transparent 7%)
        0 0 / 100% 100% no-repeat,
      /* and the field a step darker than the rim it is cut into */
      linear-gradient(var(--plate-sunk), var(--plate-sunk))
        0 0 / 100% 100% no-repeat;

  /* One dome, for an element --rivet-box square: the girder rivet's exact
     recipe — glint, dome, outline, seat, at the same fractions of its own
     radius — in bronze, centred in the box. The plate places four, one per
     corner. The joint's bolt had to move its glint IN because it is half again
     bigger than a girder rivet and the two separated into a catchlight in an
     iris; this one is SMALLER than a girder rivet, so the original fractions
     are the right ones. */
  --fx-rivet:
      radial-gradient(circle calc(var(--plate-rivet-r) * 0.26)
            at calc(50% - var(--plate-rivet-r) * 0.38)
               calc(50% - var(--plate-rivet-r) * 0.42),
          var(--joint-spec) 0 78%, transparent 79%),
      radial-gradient(circle var(--plate-rivet-r)
            at calc(50% - var(--plate-rivet-r) * 0.16)
               calc(50% - var(--plate-rivet-r) * 0.18),
          var(--joint-bolt-hi) 0 30%,
          var(--joint-bolt) 31% 66%,
          var(--joint-bolt-lo) 67% 100%, transparent 100%),
      radial-gradient(circle calc(var(--plate-rivet-r) * 1.16) at 50% 50%,
          var(--joint-lo) 0 96%, transparent 97%),
      radial-gradient(circle calc(var(--plate-rivet-r) * 1.45)
            at calc(50% + var(--plate-rivet-r) * 0.32)
               calc(50% + var(--plate-rivet-r) * 0.36),
          var(--girder-grime) 0 72%, transparent 92%);

  /* ---------- paper ----------
     The fifth generated material, and the first that is an OBJECT rather than
     a ground: a sheet nailed to the boarding. js/ctd-poster.js draws it.

     Every stock is face / back / edge — the printed side, the reverse where a
     corner has curled over, and the cut edge catching the lamp. Nothing else
     is per-stock: every mark on the sheet is --paper-mk or --paper-lt over the
     top, which is the same arrangement --wood-lit / --wood-shade give the
     boarding.

     The stocks are ordered light to dark, and that order is a REAL decision
     rather than a palette: a light stock turns a card into a bright island and
     forces dark type on it, and a dark stock keeps the page's own type
     colours. --paper-smoke is what the homepage route cards ship on for
     exactly that reason — the light-vs-dark question is a whole-site
     typography decision, and the dark stock is the one that does not make it. */
  --paper-bone:        #E4D7BE;
  --paper-bone-back:   #BCAF97;
  --paper-bone-edge:   #8E8168;

  --paper-bleach:      #EFE8D9;
  --paper-bleach-back: #C8C0B0;
  --paper-bleach-edge: #97907F;

  --paper-pulp:        #B3A895;
  --paper-pulp-back:   #8D8371;
  --paper-pulp-edge:   #6A6151;

  --paper-kraft:       #7A6149;
  --paper-kraft-back:  #5E4A38;
  --paper-kraft-edge:  #43342A;

  /* Never a top sheet — the stock that goes UNDER a dark one, in its shadow.
     See js/ctd-poster.js on why it cannot be kraft. */
  --paper-rot:         #241C19;
  --paper-rot-back:    #1B1512;
  --paper-rot-edge:    #100C0A;

  --paper-smoke:       #372C31;
  --paper-smoke-back:  #2A2126;
  --paper-smoke-edge:  #191317;

  /* The two mark colours. OPAQUE hexes, not rgba: every mark group carries its
     own opacity, so putting alpha in the token too would multiply twice and
     the whole sheet would come out a mark lighter than it was tuned. */
  --paper-mk: #1A0E0A;   /* a dark mark: fibre, crease, tear, tide line */
  --paper-lt: #FFF8E8;   /* a light one: the lit lip of a fold, a cut edge */

  /* The printed rule, and only the printed rule. It is INK on the sheet rather
     than shading of it, so it is the one mark with its own colour. */
  --paper-ink: rgba(28,18,14,0.62);

  /* What the sheet throws on the board. Blacker than --ink-950 because it is
     an occlusion and not a tint — the same call --wood-void makes. */
  --paper-shadow: #060308;

  /* ---------- the board the sheet is nailed to ----------
     A medieval tactics board: the site timber turned a quarter, then bled on
     and stood in the mud. Both marks are LIGHTER than the timber and that is
     the whole reason they are their own tokens instead of the pitch's --blood
     and --mud, which are darker than the paving they were tuned against and
     therefore invisible here. See js/ctd-poster.js. */
  --board-crust: color-mix(in srgb, var(--earth) 58%, var(--bone-500));
  --board-blood: color-mix(in srgb, var(--gore) 72%, var(--ink-950));

  /* ---- The nav facia: timber boarding in a steel channel ----
     The header is the one piece of chrome on every page, so it is built from
     the materials the page is already made of rather than from a UI palette:
     two board courses held between two rivetted flanges cut from the girder's
     own face, with the nav items as steel nameplates bolted to the wood and
     the live page in brass.

     WHY THE NAV HAS ITS OWN GEOMETRY TOKENS instead of overriding
     --girder-rivet-r locally. A custom property resolves its own var()
     references at the element that DECLARES it, so the computed value that
     inherits down is already `calc(7px * 4)` — overriding the radius further
     down the tree changes nothing that reads --rivet-box or --plate-inset,
     and three rounds of "make the bolts smaller" rendered at
     the frame's 7px because of it. Every number the facia needs is therefore
     declared HERE, at the same level as the --fx-nav-* tokens that spend it.

     Two numbers drive the bar and everything else derives: --nav-h and
     --nav-flange.

     RAISED 2026-09-16, owner: *"i want the header bar to be a bit taller, and
     the buttons for the bar to be wider/text bigger, they are too small for
     options currently."* 88/16 -> 100/18, which takes the channel from 56 to
     64. THE CHANNEL IS THE BUTTON: every casting in the bar is cut to
     `--nav-h - --nav-flange * 2` so it sits flush between the flanges, so
     "taller bar" and "taller options" are one edit, not two. The other two
     thirds of the ask are --btn-ctl-w below (width) and the rack's own
     font-size in chrome.css (label). */
  --nav-h:         100px;   /* the bar */
  --nav-flange:     18px;   /* the steel channel, top and bottom */
  --nav-plate-h:    44px;   /* the castings' floor in the bar on a phone, and the tap target */
  --nav-measure:    1360px; /* the header lines up with the frame, not the copy */

  /* ---- THE BAR IS AN OBJECT, NOT A BAND ----
     Owner, 2026-09-08: "i dont want it to go the full width of the page. it
     should fit the column. But we need some nice end pieces." So above the
     girder gate the facia stops on the FRAME'S OUTER FACES — the same two
     lines the vertical girders' backs stand on, which is the widest thing on
     the page a bar can line up with — and a bronze shoe closes each cut end.
     Below the gate there is no frame at the sides to line up with, so the bar
     runs the full width exactly as it always did, and the shoes are not shown:
     a fitting bolted to the edge of the screen is a fitting holding air, which
     is the same bargain --joint-* already makes with the tee straps.

     --nav-measure is BOTH numbers. It caps the row (chrome.css restates it as
     a width, see the note there) and it is what --nav-bar-inset measures from,
     so "how wide is the bar" is asked once. The override lives in the 1360
     block at the end of this file, next to --bay-gutter's, and BOTH tokens are
     restated there: a custom property substitutes its var() references at the
     element that DECLARES it, so an override of --nav-measure alone would move
     the row and leave the inset exactly where it was.

     A PERCENTAGE IN A CUSTOM PROPERTY resolves where it is USED, not where it
     is declared, so every consumer of --nav-bar-inset must be a full-width
     box. The board, the two flanges and the two shoes are all inset:0 layers
     inside the header, so they are. Same rule --bay-gutter lives by. */
  --nav-bar-inset:  0px;
  --nav-cap-w:      var(--girder-w);  /* the shoe is as wide as the girder is thick */

  /* --nav-rivet-r sizes the shoe's chamfer; --nav-flange-r is the dome in the
     channel's rivet run. The nav bolt this block also sized went with the
     nameplates on 2026-09-16. */
  --nav-rivet-r:    3px;
  --nav-flange-r:   4px;    /* the domes in the channel, sized to the flange */
  /* --nav-chamfer is the shoe's corner cut and what the drawer still measures
     off. It was sized to hold a nameplate bolt (4r); the plates went on
     2026-09-16 and the number stayed, because the shoes are built on it. */
  --nav-chamfer:    calc(var(--nav-rivet-r) * 4);

  /* The badge is sized in PX, not as a multiple of the bar, and that is the
     finding the lab turned up. The mark is 616x247, so every pixel of height
     costs 2.49 of ROW WIDTH — and width is what the row runs out of first. At
     a 1280 viewport the row has about 330px left once the plates and the three
     buttons have taken theirs, which caps the badge near 132px whatever the
     bar does. So a THINNER bar overhangs MORE. Tying the badge to the bar
     height hid that and quietly overflowed the row.

     Owner asked for it smaller, 2026-09-06: 119 -> 104, which is well inside
     the width cap and still hangs the pennant 16px under the lower flange.
     The overhang is the point of the badge, so the floor is --nav-h + a few
     px: at or below the bar height it stops being a sign hung on the facia
     and becomes a logo sitting in a bar.

     The bar went 88 -> 100 on 2026-09-16 and the badge went with it, 104 ->
     116: the same 16px of pennant under the lower flange. Leaving it at 104
     would have left 4px of overhang, which is the sign sitting in a bar the
     line above warns about. Arithmetic, not a second decision.

     WHAT THE TALLER BAR COSTS, measured: between about 769 and 781 the fit
     formula below caps the badge under 100px and the overhang goes to zero
     for that ~12px of viewport, because down there the row is out of width
     and the badge shrinks whatever the bar does — the same trade the note
     above describes. 781 to 851 ramps back up to the full 16px. Below 768 the
     tablet tier takes over (80px bar, 96px badge) and the 16px is back. */
  --nav-brand-max:  116px;

  /* ---- THE BADGE IS CENTRED ON A PHONE, AND WIDTH IS WHAT DECIDES IT ----
     Owner, 2026-09-08: *"on mobile. ensure the logo is in the center of the
     screen horizontally."* Below the rack's 1320 breakpoint the row is
     menu | badge | controls, and those two ends are not the same size — one
     button on the left against two or three on the right — so
     `justify-content: space-between` put the badge 24px left of centre on a
     phone and 68px left of it on a tablet. chrome.css takes the badge out of
     the row and centres it on the header instead; these are the numbers that
     stop it reaching a button once it is out of the flow.

     --nav-side IS THE HEAVIER END, derived rather than typed: three controls
     at `--nav-plate-h + --s-4` with --s-2 between them. The ≤640 tier
     redeclares it, because down there the search toggle is hidden and the
     buttons are square (see that block).

     --nav-brand-fit IS THE SAME 2.49 ARITHMETIC THE BADGE HAS ALWAYS OBEYED,
     turned round. The mark is 616x247, so a pixel of height costs 2.49 of row
     — the note above says a THINNER bar overhangs MORE for exactly this
     reason. Centred, the badge must clear --nav-side plus the row's own inset
     at BOTH ends, so the width left for it is `100vw − 2 × (side + inset +
     clearance)` and the height that fits in it is that over 2.49. 999px off
     the phone tiers, so `min()` picks the designed value everywhere else and
     this can never bite on a desktop.

     ONE FORMULA SERVES BOTH TIERS. --nav-brand-fit is declared once, in the
     1320 block; its var() references resolve against whatever --nav-side,
     --nav-row-inset and --nav-brand-clear have cascaded to on the same
     element, so the ≤640 block only has to state its three different numbers
     and the fit re-derives. Same reason the phone tiers can restate --nav-h
     and have --nav-plate-pad follow. */
  --nav-side:        calc(var(--btn-ctl-w) * 3 + var(--s-2) * 2);   /* three controls and their gaps */
  --nav-row-inset:   var(--s-4);   /* the row's clearance from the screen edge */
  --nav-brand-clear: var(--s-3);   /* badge to the nearest control */
  --nav-brand-fit:   999px;
  --nav-brand-h:     min(var(--nav-brand-max), var(--nav-brand-fit));

  /* The live plate's enamel-and-bronze palette (2026-09-07 to 2026-09-16) lived
     here. The page you are on is the gold casting now — see THE CASTING. */

  /* ---- THE FACIA IS STONE (2026-09-14, owner: "too woody — more
     metal/stone") ----

     It was two courses of timber, and with the frame gone to pewter it was the
     last woody thing above the fold and the most conspicuous: a brown band the
     width of the page, held in a cool steel channel, at the top of every
     route. The header was always designed as TWO materials — a facia seated in
     a channel — so the answer is to change the facia's material rather than to
     make it steel and lose the reason the channel reads as a channel.

     WHY A SINGLE COURSE OF ASHLAR. The band is about 60px. The old note below
     is still true of any generator asked for a bar-height strip, and it is
     true of stone for the same reason: js/ctd-ground.js lays 88-176px flags,
     and a 60px band of them is bed joints with no face between. Ashlar laid as
     ONE course has no bed joint inside the band at all — the channel flanges
     above and below ARE the bed — so the whole 60px is face, broken only by
     the perpends. That is the one masonry bond a bar-height strip can wear.

     THE PERPENDS ARE ON TWO COPRIME PERIODS (233 / 181) rather than one, so
     the blocks come out uneven lengths and the course does not read as a tiled
     pattern. This is the seam rule the wall and the flags already follow, and
     the reason no chip, pit or crack may sit at a period of its own: a landmark
     inside a repeat is how an eye finds the repeat. They ride their own coprime
     periods too.

     IT IS GREY, NOT PLUM. --slab, the same cool neutral the floor is cut from,
     and for the same reason written at --slab: a grey stone under this room's
     violet light reads as a lit wall, and a violet stone reads as a colour
     wash. The --stone-* family is the WALL's and is mixed from the plum inks;
     it would have put a third purple behind the plates.

     VALUES MATCHED TO THE TIMBER IT REPLACES — face rgb(70,67,68) against the
     old board's rgb(70,55,40) — so the nav plates, their cast shadows and the
     enamel runs off the live plate all still read exactly as they were tuned.
     Only the hue moves.

     The superseded note, kept because it is what rules out the obvious fix:
     NOT js/ctd-plank.js — that generator lays 130-200px courses for a section
     hundreds of px tall, and asked for a bar-height band it returns joints and
     voids with almost no face between them; the bar silts up to black (tried
     at --plank-scale 0.42 and 0.32, both unreadable). This recipe costs no JS,
     which the header wants anyway. */
  /* Lifted toward WHITE, not toward --bone-500. The floor's --flag-2 spends
     bone for its warm pale face and can afford to, because it is one of four
     tones scattered across a floor and the warm one is the minority. A single
     60px band of slab+bone is not a scatter — it is one flat colour the width
     of the page, and slab's neutral against bone's warm came out olive, which
     against this room's violet read as green. White keeps slab's own very
     slight blue (B 44 against R/G 38) and nothing else, which is the "cool
     neutral grey" --slab is declared to be.

     THE FACE IS HELD DOWN AT rgb(73,73,78) so the rack sits ON it. The plates
     are rgb(103,87,97); at the first lighter mix the two were 5 points apart
     and the plates melted into the wall behind them. A facia is a ground. */
  --nav-stone-lit:   color-mix(in srgb, var(--slab) 68%, white);
  --nav-stone-face:  color-mix(in srgb, var(--slab) 84%, white);
  --nav-stone-mid:   color-mix(in srgb, var(--slab) 96%, white);
  --nav-stone-deep:  color-mix(in srgb, var(--slab) 82%, black);
  /* The perpend: mortar gone, so it is the dark behind the wall showing
     through, the same argument --stone-mortar makes. */
  --nav-stone-joint: color-mix(in srgb, var(--slab) 34%, black);
  /* The cel bevel — lit band inside the top arris, dark band inside the
     bottom. Same names and structure as --stone-plateau / --stone-core and
     --flag-plateau / --flag-core, because this room models form one way. */
  --nav-stone-plateau: color-mix(in srgb, var(--bone-100) 9%, transparent);
  --nav-stone-core:    color-mix(in srgb, var(--ink-950) 34%, transparent);
  /* Pitting, and the lit lip above a pit. */
  --nav-stone-pit:     color-mix(in srgb, var(--ink-950) 44%, transparent);
  --nav-stone-pit-lit: color-mix(in srgb, var(--bone-100) 6%, transparent);
  --nav-stone-crack:   color-mix(in srgb, var(--ink-950) 58%, transparent);
  /* A knocked corner: below every face tone but above the joint, or a chip is
     indistinguishable from a hole — the note at --flag-chip, from the floor. */
  --nav-stone-chip:    color-mix(in srgb, var(--slab) 46%, black);
  --nav-stone-mark:    color-mix(in srgb, var(--bone-100) 5%, transparent);

  --fx-nav-facia:
      /* chips knocked out of the top and bottom arrises */
      radial-gradient(7px 3px at 24% 0%, var(--nav-stone-chip) 0 58%, transparent 76%) 0 0 / 421px 100% repeat-x,
      radial-gradient(5px 2.4px at 71% 100%, var(--nav-stone-chip) 0 58%, transparent 76%) 0 0 / 557px 100% repeat-x,
      /* pitting, each with a lit lip a shade above it */
      radial-gradient(circle 2.2px at 33% 62%, var(--nav-stone-pit) 0 60%, transparent 78%) 0 0 / 149px 100% repeat-x,
      radial-gradient(circle 2.6px at 33% 59%, var(--nav-stone-pit-lit) 0 44%, transparent 62%) 0 0 / 149px 100% repeat-x,
      radial-gradient(circle 1.6px at 80% 31%, var(--nav-stone-pit) 0 60%, transparent 78%) 0 0 / 97px 100% repeat-x,
      /* a weathered score across one block, and a hairline crack in another */
      radial-gradient(64px 1.1px at 52% 38%, var(--nav-stone-mark) 0 40%, transparent 100%) 0 0 / 389px 100% repeat-x,
      radial-gradient(28px 1px at 14% 72%, var(--nav-stone-crack) 0 42%, transparent 100%) 0 0 / 311px 100% repeat-x,
      /* the perpends, on two coprime periods — dark joint with a lit left
         reveal, because the light in this room comes from the left */
      linear-gradient(90deg, transparent 0 3px, var(--nav-stone-joint) 3px 6px,
          var(--nav-stone-plateau) 6px 7px, transparent 7px) 0 0 / 233px 100% repeat-x,
      linear-gradient(90deg, transparent 0 2px, var(--nav-stone-joint) 2px 5px,
          var(--nav-stone-plateau) 5px 6px, transparent 6px) 61px 0 / 181px 100% repeat-x,
      /* the cel bevel */
      linear-gradient(180deg, var(--nav-stone-plateau) 0 6%, transparent 16% 82%,
          var(--nav-stone-core) 92% 100%) 0 0 / 100% 100% no-repeat,
      /* The face, lit from above. ONE hard stop and the rest continuous: the
         hard stop is the top arris, where the stone turns into the light and
         genuinely is an edge. Four hard bands down a 60px strip is 8/25/20/7px
         of flat colour, which reads as stripes painted on a bar rather than as
         one face curving away from a light — the girder can spend five hard
         bands because a girder IS folded plate, and a stone is not. */
      linear-gradient(180deg, var(--nav-stone-lit) 0 7%, var(--nav-stone-face) 19%,
          var(--nav-stone-mid) 74%, var(--nav-stone-deep) 100%) 0 0 / 100% 100% no-repeat;
  --fx-nav-facia-edge:
      inset 0 4px 7px -3px var(--ink-950),
      inset 0 -4px 7px -3px var(--ink-950);

  /* The channel flanges: --fx-girder's face without its rivet layers — the
     same five flat bands lit from the top, the same coprime rust periods, the
     same mill grain. */
  --fx-nav-beam:
      radial-gradient(44px 30% at 40% 24%, var(--girder-rust) 0 18%, transparent 76%) 50% 0 / 197px 100% repeat-x,
      radial-gradient(30px 46% at 62% 72%, var(--girder-rust-2) 0 16%, transparent 70%) 50% 0 / 313px 100% repeat-x,
      radial-gradient(58px 22% at 24% 54%, var(--girder-rust-3) 0 14%, transparent 82%) 50% 0 / 149px 100% repeat-x,
      repeating-linear-gradient(90deg, transparent 0 26px, var(--girder-grime) 44px 62px, transparent 89px) repeat,
      repeating-linear-gradient(180deg, transparent 0 2px, var(--girder-grain) 2px 3px,
          transparent 3px 5px, var(--girder-grain-2) 5px 6px, transparent 6px 9px) repeat,
      linear-gradient(180deg,
          var(--girder-lit) 0 7%, var(--girder-face) 7% 26%, var(--girder-mid) 26% 68%,
          var(--girder-shade) 68% 90%, var(--girder-lo) 90% 100%) 0 0 / 100% 100% no-repeat;

  /* The rivet run in the channel. It is a tiled background, so it cannot be a
     border-radius circle the way the plate bolts are; it gets the other half
     of the fix instead — bands wide enough to survive at this size, and a soft
     final ramp rather than the 1%-wide hard stop in --fx-rivet, which is what
     leaves a small circle stepped. */
  /* A dome and the seat it is driven into. The offset BETWEEN the two circles
     is what makes a rim: the dome sits a shade up and left of its seat, so the
     side turned away from the light shows as a dark crescent.

     THE OFFSET MUST BE A FRACTION OF THE RADIUS, NEVER A PERCENTAGE OF THE
     TILE, and getting that wrong is what put "a black dot next to the bolt" on
     the channel. This run is tiled at --girder-pitch by 4r — 96px wide and
     16px tall — so an `at 42% 38%` dome against an `at 50% 50%` seat moved the
     dome 7.7px horizontally (8% of 96) and only 1.9px vertically (12% of 16).
     On a dome 8px across, 7.7px is a whole diameter: the seat ended up sitting
     entirely BESIDE the dome instead of under it, as its own dark disc. The
     tile is six times wider than it is tall, so percentages cannot express a
     round nudge.

     Order matters too. The seat is 1.12r, wider than the dome, so painted
     first in the list — on top — it covers the dome completely once the two
     are concentric. It belongs underneath. */
  --fx-nav-flange-rivets:
      radial-gradient(circle var(--nav-flange-r)
            at calc(50% - var(--nav-flange-r) * 0.18)
               calc(50% - var(--nav-flange-r) * 0.2),
          var(--girder-rivet-hi) 0 26%,
          var(--girder-rivet) 27% 64%,
          var(--girder-rivet-lo) 65% 94%, transparent 100%),
      radial-gradient(circle calc(var(--nav-flange-r) * 1.12) at 50% 50%,
          var(--girder-lo) 0 82%, color-mix(in srgb, var(--girder-lo) 50%, transparent) 92%, transparent 100%);

  /* A SECOND rivet run for the flanges, in iron, at a pitch that does not
     divide the first. --girder-pitch and this are coprime, so the two runs
     drift against each other the whole length of the bar and the flange never
     shows the same pair of heads twice — which is the same argument the
     girder's mutually-prime wear periods make, applied to the alloy instead
     of to the damage. */
  --nav-flange-pitch-b: calc(var(--girder-pitch) * 5 / 3);
  --fx-nav-flange-rivets-b:
      radial-gradient(circle var(--nav-flange-r)
            at calc(50% - var(--nav-flange-r) * 0.16)
               calc(50% - var(--nav-flange-r) * 0.18),
          var(--rivet-iron-hi) 0 26%,
          var(--rivet-iron) 27% 64%,
          var(--rivet-iron-lo) 65% 94%, transparent 100%),
      radial-gradient(circle calc(var(--nav-flange-r) * 1.12) at 50% 50%,
          var(--girder-lo) 0 82%, color-mix(in srgb, var(--girder-lo) 50%, transparent) 92%, transparent 100%);

  /* ---- THE SHOE ON THE END OF THE BAR ----
     What closes the facia now that it stops at the frame instead of running
     off the screen (see --nav-bar-inset). It is BRONZE, like the tee straps at
     the ends of every cross member and unlike everything else on the bar,
     because that is already this room's rule: steel is structure, bronze is
     the fitting that joins two pieces of it. A steel cap would have read as
     more bar.

     THE SILHOUETTE IS A DOG BONE, and face-on that is the honest shape: broad
     pads where it wraps the two flanges, waisted between them where there is
     only board to hold. Its pads are exactly --nav-flange deep, so the fold
     lines land on the channel's own edges and the two small heads sit in the
     flange's rivet run rather than beside it. The third head is bigger and
     alone in the middle — the tee's one big bolt, at the one place on the shoe
     with room for it.

     THE PRONGS POINT AT THE BAR (owner, 2026-09-16): a pad holds a flange, so
     it has to reach onto one, and the open end of the fork belongs against the
     channel rather than out in the margin. The square end is the outer one,
     flush with the girder's face.

     THE HEADS RIDE IN THE WAIST'S TILE, which is --fx-joint's trick and it is
     needed for the same reason: the waist is on the left of the box at the
     left-hand shoe and on the right at the right-hand one, so a per-side var()
     inside this token would resolve (to nothing) on html.ctd-v3 and take the
     whole background down to `none` with no warning. Sizing the head layers
     `var(--nav-cap-waist) 100%` puts their `at 50%` in the middle of the WAIST,
     and chrome.css then moves the group with one background-position-x.

     This is the resting state. js/ctd-steel.js draws the same fitting properly
     — chamfer, cut edge, nicks, patina — and only then sets [data-steel]. */
  --nav-cap-notch:  calc(var(--nav-cap-w) * 0.34);  /* how far the waist is cut back */
  --nav-cap-waist:  calc(var(--nav-cap-w) - var(--nav-cap-notch));
  --nav-cap-bolt-r: calc(var(--nav-flange) * 0.44); /* the one in the middle */
  --nav-cap-pad-r:  calc(var(--nav-flange) * 0.26); /* the two in the flanges */
  --fx-nav-cap-edge:
      inset 0 1px 0 var(--joint-lit),
      inset 1px 0 0 color-mix(in srgb, var(--joint-lit) 55%, transparent),
      inset 0 -2px 0 var(--joint-lo),
      inset -1px 0 0 var(--joint-lo);
  --fx-nav-cap:
      /* the big head, and the seat it is driven into */
      radial-gradient(circle var(--nav-cap-bolt-r)
            at calc(50% - var(--nav-cap-bolt-r) * 0.14) calc(50% - var(--nav-cap-bolt-r) * 0.16),
          var(--joint-bolt-hi) 0 32%, var(--joint-bolt) 33% 72%,
          var(--joint-bolt-lo) 73% 96%, transparent 100%)
        50% 0 / var(--nav-cap-waist) 100% no-repeat,
      radial-gradient(circle calc(var(--nav-cap-bolt-r) * 1.16) at 50% 50%,
          var(--joint-lo) 0 92%, transparent 96%)
        50% 0 / var(--nav-cap-waist) 100% no-repeat,
      /* the head in the top flange */
      radial-gradient(circle var(--nav-cap-pad-r)
            at calc(50% - var(--nav-cap-pad-r) * 0.16)
               calc(var(--nav-flange) / 2 - var(--nav-cap-pad-r) * 0.18),
          var(--joint-bolt-hi) 0 30%, var(--joint-bolt) 31% 70%,
          var(--joint-bolt-lo) 71% 96%, transparent 100%)
        50% 0 / var(--nav-cap-waist) 100% no-repeat,
      radial-gradient(circle calc(var(--nav-cap-pad-r) * 1.2) at 50% calc(var(--nav-flange) / 2),
          var(--joint-lo) 0 90%, transparent 96%)
        50% 0 / var(--nav-cap-waist) 100% no-repeat,
      /* and the one in the bottom flange */
      radial-gradient(circle var(--nav-cap-pad-r)
            at calc(50% - var(--nav-cap-pad-r) * 0.16)
               calc(100% - var(--nav-flange) / 2 - var(--nav-cap-pad-r) * 0.18),
          var(--joint-bolt-hi) 0 30%, var(--joint-bolt) 31% 70%,
          var(--joint-bolt-lo) 71% 96%, transparent 100%)
        50% 0 / var(--nav-cap-waist) 100% no-repeat,
      radial-gradient(circle calc(var(--nav-cap-pad-r) * 1.2) at 50% calc(100% - var(--nav-flange) / 2),
          var(--joint-lo) 0 90%, transparent 96%)
        50% 0 / var(--nav-cap-waist) 100% no-repeat,
      /* the fold lines: where the shoe turns over the channel's two edges */
      linear-gradient(180deg,
          transparent 0 calc(var(--nav-flange) - 1px),
          color-mix(in srgb, var(--joint-lit) 60%, transparent)
              calc(var(--nav-flange) - 1px) var(--nav-flange),
          transparent var(--nav-flange) calc(100% - var(--nav-flange)),
          var(--joint-lo) calc(100% - var(--nav-flange)) calc(100% - var(--nav-flange) + 1px),
          transparent calc(100% - var(--nav-flange) + 1px) 100%),
      /* grime gathering low, and the flat bronze under all of it */
      linear-gradient(180deg, transparent 0 72%, var(--joint-grime) 100%),
      linear-gradient(180deg, var(--joint-hi) 0 8%, var(--joint-face) 22% 74%, var(--joint-shade) 100%);

  /* The basket count: a brass stud on the plate's corner, not a UI dot. */
  --fx-nav-count:
      linear-gradient(180deg, var(--gold-300) 0 12%, var(--gold-500) 12% 55%, var(--gold-600) 55% 100%);
  --fx-nav-count-edge:
      inset 0 1px 0 color-mix(in srgb, var(--bone-100) 60%, transparent),
      0 1px 2px var(--ink-950);

  /* The badge stands off the bar it is hung over. A clip-path cuts a box-shadow
     off with the corners, so the cast is a filter, which follows the clipped
     silhouette. */
  --fx-nav-brand-cast: drop-shadow(0 7px 8px color-mix(in srgb, var(--ink-950) 88%, transparent));
  /* The glyph is CUT into the plate: dark under the stroke, a lit lip above
     it. A drop-shadow follows the glyph, which a text-shadow cannot do. */
  --fx-nav-icon:
      drop-shadow(0 1px 0 var(--girder-lo))
      drop-shadow(0 -1px 0 color-mix(in srgb, var(--girder-rivet-hi) 45%, transparent));
  --fx-nav-icon-on: drop-shadow(0 1px 0 color-mix(in srgb, var(--gold-600) 80%, black));

  /* THE ACTION PLATES (2026-09-07 to 2026-09-16) lived here: brass and pewter
     plates with two painted bolts, the site's buttons before the casting
     below. The recipe is in git under those dates; the decision to retire it
     is in the vault's Decision Log. */

  /* ============================================================
     THE CASTING (2026-09-16)
     ============================================================
     The action plates (2026-09-07) were the site's buttons until today.
     Five rounds of trying to improve them ended with the owner starting
     again: "fully redesign my buttons... start from scratch, ignore what
     exists already." Then "push these onto the website across the rest of
     the page": the nav rack, the wizard beats and the filter chips took the
     casting too, and the plate tokens came out. (local-dev/button-lab.html is
     the sheet that was approved.)

     WHAT A BUTTON IS NOW: ONE CAST PIECE. A solid slab with a 45-degree
     bevel running all the way round it — lit on the faces that turn to the
     light, dark on the faces that turn away — a gently domed face, and the
     label engraved into it. No fixings, no frame, no panel inside a frame.
     One object, which is what makes it read as pressable: you can see the
     edge you would push. Pressing it REVERSES the bevel's light, because the
     faces that stood above the surface are now below it.

     Why it stands out where the plates did not: they were mid-toned brass on
     a dark ground with a lot of small texture — low contrast, busy, small.
     This is the site's own gold at full strength, clean, one big bevel, one
     big label. Contrast and size are what "standing out" is made of, and
     the heights step up from 40/44/52 to 40/48/56 for the same reason.

     THE BEVEL IS ONE GRADIENT. A 135deg run from lit to dark across a rim
     only --btn-bevel wide reads, on every edge, as a chamfer turned toward
     or away from a light at the top-left. That is the whole trick, and why
     the rim is a pseudo-element under the face rather than a border: a
     border cannot be lit on two sides and shaded on the other two. The
     silhouette is a clip-path, so the cast shadow is a drop-shadow FILTER —
     it follows the cut corners, where a box-shadow would draw a rectangle
     behind an octagon.

     Three forms, approved together: the octagon (default), --inlay (the
     octagon with the gold moved off the face onto the rim and the letters —
     the night version) and --pennant (flat top, bottom corners cut deep: a
     tab that hangs from whatever sits above it, with a hard drop plate). */
  --btn-h:        48px;
  --btn-h-sm:     40px;
  --btn-h-lg:     56px;
  --btn-cut:      10px;   /* the corner clip */
  --btn-cut-sm:    7px;
  --btn-cut-lg:   12px;
  --btn-cut-pen:  14px;   /* the pennant's deep bottom corners */
  --btn-cut-pen-sm: 10px;
  --btn-bevel:     3px;   /* rim depth */
  --btn-bevel-sm:  2px;
  --btn-ctl-w:    84px;   /* a header control; 74 from the rack's breakpoint down — see the 1320 block */

  /* the metals, mixed from the room and nothing else */
  --btn-gold-hi:   color-mix(in srgb, var(--gold-300) 68%, var(--bone-100));
  --btn-gold-deep: color-mix(in srgb, var(--gold-600) 52%, var(--ink-950));
  --btn-gun-hi:    color-mix(in srgb, var(--bone-500) 46%, var(--ink-950));
  --btn-gun:       color-mix(in srgb, var(--bone-500) 26%, var(--ink-950));
  --btn-gun-lo:    color-mix(in srgb, var(--bone-500) 9%,  var(--ink-950));
  --btn-ink-hi:    color-mix(in srgb, var(--ink-950) 72%, var(--bone-500));
  --btn-ink:       color-mix(in srgb, var(--ink-950) 88%, var(--bone-500));
  --btn-lit-edge:  color-mix(in srgb, var(--bone-100) 32%, transparent); /* under an engraved dark letter */
  --btn-cut-edge:  color-mix(in srgb, var(--ink-950) 60%, transparent);  /* under an engraved light one */
  --btn-hot-ink:   color-mix(in srgb, var(--gold-300) 70%, var(--bone-100)); /* gold letters, under the pointer */

  /* the bevel, lit and seated, in each metal */
  --fx-btn-rim-gold:       linear-gradient(135deg, var(--btn-gold-hi) 0%, var(--gold-500) 48%, var(--btn-gold-deep) 100%);
  --fx-btn-rim-gold-press: linear-gradient(135deg, var(--btn-gold-deep) 0%, var(--gold-500) 52%, var(--btn-gold-hi) 100%);
  --fx-btn-rim-gun:        linear-gradient(135deg, var(--btn-gun-hi) 0%, var(--btn-gun) 48%, var(--btn-gun-lo) 100%);
  --fx-btn-rim-gun-press:  linear-gradient(135deg, var(--btn-gun-lo) 0%, var(--btn-gun) 52%, var(--btn-gun-hi) 100%);
  /* the faces: domed, lit at the top. The sheen (--fx-sheen) rides as the
     face's FIRST background layer in the stylesheet, so a hover rakes it
     across with one background-position change and no extra element. */
  --fx-btn-face-gold:  linear-gradient(180deg, var(--btn-gold-hi) 0%, var(--gold-500) 38%, var(--gold-600) 100%);
  --fx-btn-face-gun:   linear-gradient(180deg, var(--btn-ink-hi) 0%, var(--btn-ink) 45%, var(--ink-950) 100%);
  --fx-btn-face-inlay:
      radial-gradient(120% 130% at 50% 120%, color-mix(in srgb, var(--gold-600) 34%, transparent) 0%, transparent 68%),
      linear-gradient(180deg, var(--btn-ink-hi) 0%, var(--btn-ink) 45%, var(--ink-950) 100%);
  /* the letters, ENGRAVED: a dark letter on gold takes a hairline of light
     under its lower edge; a gold letter on dark takes a dark cut and a
     little of its own light */
  --fx-btn-engrave-dark:  0 1px 0 var(--btn-lit-edge), 0 -1px 0 color-mix(in srgb, var(--ink-950) 18%, transparent);
  --fx-btn-engrave-light: 0 1px 0 var(--btn-cut-edge), 0 0 10px color-mix(in srgb, var(--gold-500) 25%, transparent);
  --fx-btn-engrave-lit:   0 0 12px color-mix(in srgb, var(--gold-300) 55%, transparent), 0 1px 0 var(--btn-cut-edge);
  --fx-btn-engrave-hot:   0 0 18px color-mix(in srgb, var(--gold-300) 80%, transparent), 0 1px 0 var(--btn-cut-edge);
  --fx-btn-engrave-plain: 0 1px 0 var(--btn-cut-edge);   /* letters that are not gold: the cut, no glow */
  /* the cast shadow follows the clipped silhouette; --hot adds the lamp
     finding it, --press is the seated casting, --off is a disabled one */
  --fx-btn-cast:
      drop-shadow(0 3px 2px color-mix(in srgb, var(--ink-950) 45%, transparent))
      drop-shadow(0 8px 12px color-mix(in srgb, var(--ink-950) 35%, transparent));
  --fx-btn-cast-hot:
      drop-shadow(0 3px 2px color-mix(in srgb, var(--ink-950) 45%, transparent))
      drop-shadow(0 8px 12px color-mix(in srgb, var(--ink-950) 35%, transparent))
      drop-shadow(0 0 14px color-mix(in srgb, var(--gold-500) 55%, transparent));
  --fx-btn-cast-press: drop-shadow(0 1px 1px color-mix(in srgb, var(--ink-950) 50%, transparent));
  --fx-btn-cast-off:   saturate(.4) drop-shadow(0 2px 2px color-mix(in srgb, var(--ink-950) 30%, transparent));
  /* the pennant's hard drop plate: a second copy of the silhouette, offset,
     in ink. Graphic rather than material, which is the point of that form. */
  --fx-btn-plate:       drop-shadow(4px 4px 0 var(--ink-950));
  --fx-btn-plate-hot:   drop-shadow(4px 4px 0 var(--ink-950)) drop-shadow(0 0 14px color-mix(in srgb, var(--gold-500) 55%, transparent));
  --fx-btn-plate-press: drop-shadow(2px 2px 0 var(--ink-950));
  --fx-btn-plate-sm:    drop-shadow(3px 3px 0 var(--ink-950));

  /* ---- More hardware (2026-09-07, owner: "add in some metal and verdigist
     around the place more") ----
     Five placements, and each one is a thing that WOULD be made of metal if
     the page were a real dugout: the team tiles are stamped tags, the crest
     sits in a bezel, the compare divider is a boss, a band between two cross
     members is a rivetted seam, and a text field is a channel. Nothing here is
     metal for the sake of it — the rule this design keeps failing and
     relearning is that hardware has to be a FITTING, not a finish.

     Verdigris follows the brass and only the brass, in the seats: the bezel's
     groove, the boss's rim and the seam's rivet holes. The pewter gets none —
     it is the one alloy here that neither rusts nor greens, which is exactly
     why the tiles can carry it thirty-one times without the section turning
     into weather. */

  /* THE TAG. A stamped pewter plate, which is what the product IS. Bands
     only, NO mill grain: the grain tokens are opaque and a hard stripe
     pattern behind a 14px label breaks the amplitude rule in DESIGN-SYSTEM
     §2 — a smooth vertical ground does not. */
  --fx-tag-face:
      linear-gradient(180deg,
          var(--iron-lit) 0 4%, var(--iron-face) 4% 46%,
          var(--iron-mid) 46% 92%, var(--iron-shade) 92% 100%);
  --fx-tag-edge:
      inset 0 1px 0 var(--iron-rivet-hi),
      inset 1px 0 0 color-mix(in srgb, var(--iron-rivet-hi) 34%, transparent),
      inset 0 -2px 0 var(--iron-lo),
      0 2px 4px -1px color-mix(in srgb, var(--ink-950) 72%, transparent);
  --fx-tag-edge-hover:
      inset 0 1px 0 var(--iron-rivet-hi),
      inset 1px 0 0 color-mix(in srgb, var(--iron-rivet-hi) 48%, transparent),
      inset 0 -2px 0 var(--iron-lo),
      0 8px 16px -6px color-mix(in srgb, var(--ink-950) 82%, transparent);

  /* THE BEZEL. A brass ring the crest is seated into — turned, so it is lit
     top-left and shadowed bottom-right, with the verdigris down in the groove
     where the disc meets the ring and nothing ever gets at it. */
  --fx-bezel:
      radial-gradient(circle at 34% 30%,
          transparent 0 62%, var(--verd-seat) 64% 72%, transparent 74%),
      conic-gradient(from 210deg,
          var(--joint-lit) 0turn, var(--joint-face) 0.22turn,
          var(--joint-lo) 0.5turn, var(--joint-face) 0.78turn,
          var(--joint-lit) 1turn);
  --fx-bezel-edge:
      inset 0 0 0 1px color-mix(in srgb, var(--joint-lo) 76%, transparent),
      inset 0 1px 0 color-mix(in srgb, var(--joint-spec) 42%, transparent),
      0 1px 2px color-mix(in srgb, var(--ink-950) 74%, transparent);

  /* THE BOSS. The compare divider was a circle with a glow on it. A boss is a
     turned bronze disc: a domed face, a lit crown, a rim it is seated in, and
     the green in that rim. */
  --fx-boss:
      radial-gradient(circle at 38% 30%,
          var(--joint-lit) 0 12%, var(--joint-face) 42%,
          var(--joint-shade) 74%, var(--joint-lo) 100%);
  --fx-boss-edge:
      inset 0 0 0 2px var(--verd-seat),
      inset 0 0 0 3px color-mix(in srgb, var(--joint-lo) 82%, transparent),
      inset 0 2px 0 -1px color-mix(in srgb, var(--joint-spec) 46%, transparent),
      0 3px 6px -2px color-mix(in srgb, var(--ink-950) 80%, transparent);

  /* THE SEAM. A run of brass rivets along the edge of a band, with the green
     in each hole. The RULE sets the pitch through background-size, so one
     token serves the trust band and the footer's top edge at two spacings. */
  /* 4.2px, so the dome is 8.4px across. Not smaller: DESIGN-SYSTEM records
     why .ctd-seam was killed — at ornament size a lit dot reads as a little
     bone, and what makes one of these hardware is scale plus form (glint,
     dome, turned-away rim, seat). Below about 8px the form stops resolving
     and only the dot survives. */
  --seam-rivet-r: 4.2px;
  --fx-seam-rivets:
      radial-gradient(circle calc(var(--seam-rivet-r) * 0.3)
            at calc(50% - var(--seam-rivet-r) * 0.34) calc(50% - var(--seam-rivet-r) * 0.38),
          var(--joint-spec) 0 74%, transparent 75%),
      radial-gradient(circle var(--seam-rivet-r)
            at calc(50% - var(--seam-rivet-r) * 0.14) calc(50% - var(--seam-rivet-r) * 0.16),
          var(--joint-bolt-hi) 0 30%, var(--joint-bolt) 31% 66%,
          var(--joint-bolt-lo) 67% 100%, transparent 100%),
      radial-gradient(circle calc(var(--seam-rivet-r) * 1.5) at 50% 50%,
          var(--verd-seat) 0 66%, transparent 67%),
      radial-gradient(circle calc(var(--seam-rivet-r) * 1.18) at 50% 50%,
          var(--joint-lo) 0 92%, transparent 93%);
  --fx-seam-edge:
      inset 0 1px 0 color-mix(in srgb, var(--joint-lit) 40%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--ink-950) 72%, transparent);

  /* THE CHANNEL. A text field is a slot milled into the panel, not a box drawn
     on it: dark at the top where the lip stands over it, a lit lower edge
     where the light reaches the bottom of the cut. */
  --fx-channel:
      inset 0 2px 3px -1px color-mix(in srgb, var(--ink-950) 78%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--iron-rivet-hi) 26%, transparent);

  /* ============================================================
     THE PARTS TRAY — a roster card, a bench ticket, an empty bay.
     ============================================================
     Owner, 2026-09-13: *"the cards etc are too bland / don't match the rest of
     the styling... they are just blank purple."* They were: `--fx-card-ground`
     plus a hairline, which is a rectangle with a light on it, sat in a page of
     girders, planks, riveted plates and cast bronze.

     THE OBJECT DECIDES THE TREATMENT. A roster card holds the drawn sprue the
     customer is sent, so the card is the TRAY that part is seated in — pressed
     sheet with a formed lip, a milled well, and rivets down both flanges. That
     is a thing this workshop would own, which is the whole argument: the page
     is a parts store, not a dashboard.

     BIMETAL, AND THE BODY IS THE COLD HALF. [[bimetal-frame-and-bolt-alloys]]
     is already the site's rule — a brass web with the contrast in the studs —
     and it decides this too: the tray body stays in the plum/iron family so
     sixteen of them in a grid do not fight the brass frame around them, and
     every warm note is HARDWARE (the bay tag, the seam rivets). Sixteen brass
     trays would be a gold page with a sprue lost in the middle of it.

     ALL GRADIENTS, NO NEW DOM AND NO JS. A roster is up to sixteen of these,
     each already carrying a generated SVG; anything per-card that needed a
     paint pass of its own would be paid for sixteen times. */

  /* The face. Top layer first: the light falling over the formed lip, a fine
     vertical brush (sheet steel is rolled, and the direction matters — across
     the card it would read as timber, the trap --fx-plate already documents),
     and the flat body. */
  /* THE TRAY IS LIT AND THE PART IS NOT, and that value gap is the whole
     effect — not the grain, not the bolts. Measured before this was written:
     the card was #351D3A and the drawn part's ground is a #2E3138 mix, i.e.
     the SAME luminance to within three points. A dark object on an equally
     dark tray is not an object on a tray, it is one flat shape, and twelve of
     them in a grid is what "blank purple" actually describes.

     So the pressing is raised nine points into the light while the part stays
     where it is, and a sprue suddenly sits IN something. The lift is a flat
     bone wash rather than a lighter plum, because plum lightens towards pink
     and the trays would go mauve; bone over plum goes pewter, which is the
     third metal the badge already carries. */
  --fx-tray:
      linear-gradient(180deg,
          color-mix(in srgb, var(--bone-100) 10%, transparent) 0 1px,
          color-mix(in srgb, var(--bone-100) 5%, transparent) 1px 5%,
          transparent 26%),
      repeating-linear-gradient(90deg,
          color-mix(in srgb, var(--bone-100) 2.2%, transparent) 0 1px,
          transparent 1px 3px),
      linear-gradient(180deg,
          transparent 0 58%,
          color-mix(in srgb, var(--ink-950) 34%, transparent) 100%),
      linear-gradient(0deg,
          color-mix(in srgb, var(--bone-100) 9%, transparent) 0 100%);

  /* The thickness. A tray is pressed from sheet, so the lit edge is a hairline
     and the shadow under it is the DEPTH of the pressing — not a drop shadow
     under a floating card, which is the thing that makes a panel look like it
     is hovering over a photo. */
  --fx-tray-edge:
      inset 0 1px 0 color-mix(in srgb, var(--iron-spec) 34%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--ink-950) 82%, transparent),
      inset 1px 0 0 color-mix(in srgb, var(--iron-lit) 12%, transparent),
      inset -1px 0 0 color-mix(in srgb, var(--ink-950) 60%, transparent),
      0 2px 5px -2px color-mix(in srgb, var(--ink-950) 72%, transparent);

  /* Lifted into the light — hover only, and it is the same fitting, not a new
     one: the rim brightens and the shadow lengthens by two pixels. */
  --fx-tray-edge-lit:
      inset 0 1px 0 color-mix(in srgb, var(--iron-spec) 68%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--ink-950) 82%, transparent),
      inset 1px 0 0 color-mix(in srgb, var(--iron-lit) 26%, transparent),
      inset -1px 0 0 color-mix(in srgb, var(--ink-950) 60%, transparent),
      0 6px 14px -4px color-mix(in srgb, var(--ink-950) 78%, transparent);

  /* THE LIP. The band the tray is folded over at the top: lit along its own
     crown, dark in the fold under it, so the header sits ON the rim rather
     than inside the box. */
  --fx-tray-lip:
      linear-gradient(180deg,
          color-mix(in srgb, var(--bone-100) 7%, transparent) 0 1px,
          color-mix(in srgb, var(--bone-100) 2.6%, transparent) 1px 40%,
          color-mix(in srgb, var(--ink-950) 26%, transparent) 100%);
  --fx-tray-lip-edge:
      inset 0 -1px 0 color-mix(in srgb, var(--ink-950) 88%, transparent),
      0 1px 0 color-mix(in srgb, var(--iron-lit) 16%, transparent);

  /* THE WELL. Deeper than --fx-channel, which is a milled slot for text: this
     is a recess a part sits down inside, so the top wall throws further and
     the bottom lip catches more light. */
  --fx-tray-well:
      inset 0 3px 6px -2px color-mix(in srgb, var(--ink-950) 88%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--iron-rivet-hi) 22%, transparent),
      inset 2px 0 4px -3px color-mix(in srgb, var(--ink-950) 70%, transparent),
      inset -2px 0 4px -3px color-mix(in srgb, var(--ink-950) 70%, transparent);

  /* AN EMPTY BAY IS NOT A DASHED RECTANGLE. It is the same pressing with
     nothing in it and no light on it, plus the diagonal caution hatch a
     workshop paints on a space that is meant to be filled. Kept to two thin
     strokes at 3% — a real hazard stripe at real contrast would shout louder
     than the rosters that are full. */
  --fx-tray-vacant:
      repeating-linear-gradient(135deg,
          color-mix(in srgb, var(--bone-100) 3.2%, transparent) 0 1px,
          transparent 1px 9px),
      linear-gradient(180deg,
          color-mix(in srgb, var(--ink-950) 24%, transparent) 0 100%);

  /* THE DECK the trays are racked on. Tread plate — the chequered steel a
     gantry or a workshop floor is made of, which is the one piece of this
     building's ironwork the site had not used yet and the obvious ground for a
     rack of parts. Two hatches crossing at opposite angles; where they overlap
     they add, which is what draws the lozenges, so the pattern is two
     gradients rather than a tile.

     3% AND 2.4%, WHICH LOOKS TOO LOW WRITTEN DOWN. Almost all of this surface
     is covered by trays — only the gaps and the border show — so the tread is
     read at the edges of the eye, and at the alpha a floor would want it the
     gaps turn into a herringbone that competes with twelve drawings. It is a
     material tell, not a feature. */
  --fx-deck:
      repeating-linear-gradient(38deg,
          transparent 0 9px,
          color-mix(in srgb, var(--bone-100) 3%, transparent) 9px 20px,
          transparent 20px 40px),
      repeating-linear-gradient(-38deg,
          transparent 0 9px,
          color-mix(in srgb, var(--bone-100) 2.4%, transparent) 9px 20px,
          transparent 20px 40px),
      linear-gradient(180deg,
          color-mix(in srgb, var(--ink-950) 62%, transparent) 0 100%);
  /* The rack is a shallow crate: the deck sits BELOW the trays standing on it,
     so the light stops at its lip rather than running over it. */
  --fx-deck-edge:
      inset 0 2px 5px -2px color-mix(in srgb, var(--ink-950) 86%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--iron-rivet-hi) 14%, transparent),
      inset 0 0 0 1px color-mix(in srgb, var(--ink-950) 60%, transparent);

  /* THE TAG'S FACE AS ONE LAYER. The same four-band ramp the drawing paints
     across a moulded tag — crown, crown, belly, foot — so an HTML tag and a
     drawn one are lit identically. Every stop is derived from --tag-face, so
     the pigment table in parts.css still decides the colour and this decides
     only the modelling. */
  --fx-tag-plate: linear-gradient(180deg,
      var(--tag-crown-2) 0 6%,
      var(--tag-crown) 6% 44%,
      var(--tag-belly) 44% 88%,
      var(--tag-foot) 88% 100%);

  /* A CODE CAST INTO A TAG. The drawing letters its codes with a real second
     pass in --tag-ink-lo; in HTML that is one shadow under the glyph, which is
     the same statement at the same size. */
  --fx-tag-cast: 0 1px 0 var(--tag-ink-lo);

  /* The numeral CAST INTO the bay tag — one pixel of the metal's own highlight
     under the glyph, which is what a struck number looks like on brass. */
  --fx-tag-emboss: 0 1px 0 color-mix(in srgb, var(--joint-spec) 45%, transparent);
  /* And the numeral STRUCK INTO an empty bay: the same light one pixel the
     other way, because the mark is sunk rather than raised. */
  --fx-bay-deboss: 0 1px 0 color-mix(in srgb, var(--bone-100) 8%, transparent);
  /* A drilled hole with no bolt in it — an unfilled bay is not bolted shut. */
  --fx-bolt-hole: radial-gradient(circle 3.2px at 50% 50%,
      color-mix(in srgb, var(--ink-950) 74%, transparent) 0 88%, transparent 89%);

  /* ---- Z ---- */
  --z-header: 100; --z-sticky: 90; --z-dialog: 1000; --z-toast: 1100;

  /* ---- Motion ---- */
  --t-fast: 120ms; --t-base: 200ms; --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
}

/* ---- The gutter opens ----
   Same 1360 gate the vertical girders sit behind, and necessarily the same
   one: --bay-gutter is where the frame's inside begins, and below 1360 there
   is no frame at the sides for anything to stop against. Above it every layer
   that used to run off the screen — the cross member, a section's boarding, an
   alt section's ground — ends here instead, and what shows outside is the
   pitch paving the whole page stands on.

   Declared on html.ctd-v3 / .ctd-scope like the rest of the frame's numbers,
   and resolved by whichever full-width box uses it (see --bay-gutter). */
@media (min-width: 1360px) {
  html.ctd-v3, .ctd-scope { --bay-gutter: calc(50% - var(--wrap) / 2); }

  /* ---- and the bar stops where the frame stops ----
     --wrap plus a girder either side IS the frame's outer face, so the facia's
     ends come down plumb on the backs of the two columns holding the page up.
     Both tokens, not just the first: a custom property substitutes its var()
     references at the element that DECLARES it, so overriding --nav-measure
     alone would move the row and leave the inset exactly where it was.

     THE ONE SELECTOR ON THIS PAGE THAT IS NOT `.ctd-scope` TOO, and it has to
     be. The chrome is v3 on every route, but the FRAME is not: footer.php only
     emits the ground, the gutters and the girders behind ctd_is_v3_page(), so
     on a legacy route there would be nothing at the sides for an inset bar to
     line up with — just the old page showing past both ends of it. Scoping
     the override under html.ctd-v3 leaves those routes the full-width bar they
     have always had, and the two shoes in chrome.css are gated the same way.

     The two shoes live in the margin OUTSIDE the frame, and --nav-cap-w is
     --girder-w, so between them they measure --wrap + 4 girders = 1360 — which
     is the gate. At the first width that shows any of this the shoes fill the
     margin exactly; below it there is no margin for them, which is the same
     reason the girders themselves stop here. */
  html.ctd-v3, html.ctd-v3 .ctd-scope {
    --nav-measure: calc(var(--wrap) + var(--girder-w) * 2);
    --nav-bar-inset: calc(50% - var(--nav-measure) / 2);
  }
}

/* ---- The badge's width cap, from the rack's own breakpoint down ----
   1320 is where chrome.css folds the nav into the drawer, which is exactly
   where the row becomes menu | badge | controls and the badge starts being
   centred. Above it the badge is the row's left-hand anchor with the rack
   beside it and nothing needs capping — and the sum would be wrong anyway,
   because from 1360 the row is --nav-measure wide rather than the screen. */
@media (max-width: 1320px) {
  html.ctd-v3, .ctd-scope {
    /* The control goes back to 74 at the same breakpoint, for the same reason
       the badge starts being capped at it: --nav-side is three of these and a
       centred badge has to clear that end TWICE, so a px on the control costs
       the badge 2.4px of height. Above 1320 the rack is beside the badge and
       there is no cap at all, so the wide control is free up there and is not
       down here. --nav-side and --nav-brand-fit are declared on this same
       element, so both re-derive from this without being restated. */
    --btn-ctl-w: 74px;
    --nav-brand-fit:
        calc((100vw - (var(--nav-side) + var(--nav-row-inset) + var(--nav-brand-clear)) * 2) / 2.49);
  }
}

/* ---- The steel at phone widths ----
   The vertical girders never show below 1360, but the cross members and the
   nameplates do, and a 40px bar with 14px domes is a third of a phone's
   section gap. Everything about the steel derives from these three numbers, so
   scaling them scales the bar, the plate, its rivets, its clearance and its
   pitch grid together. 64 is 2^6, so the wear periods (149 / 197 / 313, all
   prime) still share no factor with the pitch. */
@media (max-width: 768px) {
  html.ctd-v3, .ctd-scope {
    --girder-w: 28px;
    --girder-rivet-r: 5px;
    --girder-pitch: 64px;
    /* The facia at tablet width, raised with the desktop bar on 2026-09-16
       (72/14 -> 80/16, a 48px channel). Only the numbers the bar derives from
       move: the shoe's two fixing radii (--nav-cap-bolt-r, --nav-cap-pad-r)
       and the castings' height re-derive from --nav-flange because they are
       declared on this same element and resolve here. (The list that used to
       sit in this comment named --nav-plate-pad, --nav-bolt-x and
       --nav-rust-origin; all three went with the nameplates.) */
    --nav-h: 80px;
    --nav-flange: 16px;
    --nav-brand-max: 96px;
    --nav-flange-r: 3.5px;
  }
}

/* ---- The facia on a phone ----
   A sticky 88px bar with a 31px sign under it is a quarter of a phone
   screen before the page starts. The nav rack is in the drawer by this
   width, so the row is badge + menu + basket and the badge is no longer
   capped by the row: it is capped by how much of the viewport the header
   may take. 60 + 14 of overhang. */
@media (max-width: 640px) {
  html.ctd-v3, .ctd-scope {
    --nav-h: 60px;
    --nav-flange: 12px;
    --nav-plate-h: 40px;
    --nav-brand-max: 74px;
    --nav-flange-r: 3px;
    /* The row's three numbers at phone size. The search toggle is hidden down
       here and the controls are square (chrome.css), so the heavy end is two
       40px plates rather than three 60px ones — and every 1px this end loses
       is 2 the badge gains, because the badge has to clear it TWICE. The inset
       drops to 8px and the clearance to 4px for the same reason: at 360px the
       row has literally zero slack, and those two changes are worth 7px of
       badge height between them. */
    --nav-side: calc(var(--nav-plate-h) * 2 + var(--s-2));
    --nav-row-inset: var(--s-2);
    --nav-brand-clear: var(--s-1);
  }
}

/* ---- Colour-blind mode ----
   The category hues are the ONLY thing body.cb-mode changes: every dot,
   chip and category header keys off --cat-*, so redefining them here is the
   whole feature — no second rule anywhere. Values are the Okabe-Ito CVD-safe
   palette, the same hues css/core/variables.css uses on the legacy tree, so
   the two trees look identical in this mode.

   THE EIGHTH CATEGORY BROKE THE ONE-TO-ONE, AND WENT ACHROMATIC. Okabe-Ito has
   eight members and seven were already spent; the one left is black, which on
   a #110A16 page is not a colour, it is a hole. So Characteristic takes white
   instead. That is not a departure from the palette's promise — white is
   achromatic, so it is separable from all seven under every CVD type by the
   one channel that never fails, and against this ground it separates by
   luminance too. The near-miss to watch is Trait's yellow (#F0E442), which is
   the lightest of the seven; white clears it on hue (neutral vs. yellow)
   rather than on lightness, and the dot never travels without its label
   (DESIGN-SYSTEM.md §7), which is what makes that margin enough. */
html.ctd-v3 body.cb-mode, body.cb-mode {
  --cat-g: #009E73;   /* General   · bluish green */
  --cat-a: #56B4E9;   /* Agility   · sky blue */
  --cat-s: #D55E00;   /* Strength  · vermillion */
  --cat-p: #CC79A7;   /* Passing   · reddish purple */
  --cat-m: #E69F00;   /* Mutation  · orange */
  --cat-d: #0072B2;   /* Devious   · blue */
  --cat-t: #F0E442;   /* Trait     · yellow */
  --cat-c: #FFFFFF;   /* Character.· white — the palette's eighth is black */
}

@media (prefers-reduced-motion: reduce) {
  html.ctd-v3, .ctd-scope { --t-fast: 0ms; --t-base: 0ms; }
}
