/* ═══════════════════════════════════════════════════════════
   studio-pretty-websites — Google AI Studio design language
   dark #121317 · surfaces #1a1b1f/#212226 · accent blue #2E96FF
   Google Sans Flex (display) · Inter (body) · Google Sans Code (mono)
   ═══════════════════════════════════════════════════════════ */

:root {
  /* SPACING LADDER — 4pt, with 2px as the single sub-step. See tools/spacing-scale.mjs.
     556 literal values across 34 sizes became these thirteen. */
  --s-05: 2px;
  --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-20: 80px;
  --s-24: 96px;

  /* SPACING LADDER — 4pt, with 2px as the single sub-step. See tools/spacing-scale.mjs.
     556 literal values across 34 sizes became these thirteen. */
  --s-05: 2px;
  --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-20: 80px;
  --s-24: 96px;

  /* Radius ladder — see tools/radius-scale.mjs. Three rungs and a pill; 50% stays 50%. */
  --r-s: 8px;
  --r-m: 12px;
  --r-l: 16px;
  --r-pill: 999px;

  /* TYPE SCALE. Before this there were 42 distinct font sizes, 43 of them half-pixel values that
     accumulated rather than being chosen, and the eyebrow role was implemented ~20 times at seven
     different sizes. Five rungs cover every role on the site; the two clamp() ramps for h1 and h2
     were already deliberate and are untouched. */
  --t-eyebrow: 11px;   /* mono, uppercase, tracked — labels, counts, kickers */
  --t-micro:   12px;   /* captions, meta */
  --t-body-s:  13px;   /* dense prose, table cells, card copy */
  --t-body:    15px;   /* reading prose */
  --t-h3:      17px;
  /* --t-h2 was used by three rules in the teardown modal and defined nowhere, so each of those
     declarations was invalid and dropped in silence: the section headings fell back to inherited
     body size and the hierarchy they were written to create did not exist. An undefined custom
     property does not warn, does not fail a build, and does not show up as anything but a heading
     that looks slightly wrong. */
  --t-h2:      21px;   /* card and panel titles */

  /* Filled buttons need a darker blue: #fff on --blue is 3.03:1 and on --blue-bright 2.15:1,
     so the primary action on the gate — the first screen of the site — failed hardest. --blue
     stays as it is for borders and the active-tab underline, where 3:1 is the bar. */
  --blue-fill: #0b62c4;

  /* Status colours. The set had --blue/--green/--amber and no red, no purple, and none of the
     soft tints the pills use, so every status colour on the site was written as a literal and the
     same green appeared as three slightly different hexes. Named here once. */
  --red: #f0a0a0;
  --red-deep: #6a3a3a;
  --purple: #c58af9;
  --green-soft: rgba(91, 209, 139, .13);
  --amber-soft: rgba(249, 171, 0, .13);
  --red-soft: rgba(220, 80, 80, .13);
  --scrim: rgba(8, 9, 10, .82);

  --bg: #121317;
  --bg-deep: #0e0f12;
  --surface: #1a1b1f;
  --surface-2: #212226;
  --surface-3: #26272c;
  --border: rgba(255,255,255,0.09);
  --border-strong: rgba(255,255,255,0.15);
  --text: #e8eaed;
  --white: #ffffff;
  --text-2: #9aa0a6;
  --text-3: #8a9096;   /* was #6b7076 — 3.44:1 on --surface, failing AA at every one of ~20 call
                        sites, all of them <=12px normal text. 4.54:1 on --surface-3 now. */
  --blue: #2e96ff;
  --blue-bright: #6bb6ff;
  --blue-soft: rgba(46,150,255,0.14);
  --blue-border: rgba(46,150,255,0.42);
  /* THE CHIP SURFACE, ONE DEFINITION. A chip on a diagram and a chip in a card are the same object
     in this site's grammar — a raised tile carrying one action or one name — and the value was
     written literally rather than tokenised. On the Graph tab `.vc-sec,.vc-node` carried
     `rgba(255,255,255,.055)` with NO light-theme counterpart, so the affordance its own comment
     describes ("reader chips get a raised surface") painted white-on-white and did not exist on
     the light theme. Same value, same meaning, one token that flips with the theme.
     /method declares the identical pair inline in build-method.mjs as `--mth-chip`. That file is
     not ours to edit today; when it is next touched, point it here and delete the local copy. */
  --dgm-chip: rgba(255,255,255,0.055);
  --dgm-chip-hi: rgba(255,255,255,0.09);

  /* ── THE OPEN-HERE MARK ──────────────────────────────────────────────────────────────────────
     A raised chip surface (`--dgm-chip` above) says "this tile is an object". It does NOT say
     "this one opens something and the fourteen beside it do not", and on the Graph tab that was
     the whole defect: a law chip and a corpus-query chip both measured 11px / 400 / #9aa0a6 /
     23.6px tall, differing only by a 5.5%-alpha wash that is invisible at any size. LA: "it is not
     clear when we can click to see laws. That pill should have special color."

     So the strongest affordance this site has — a FILLED accent, the same one `button.chip.on` and
     `.card-chips .chip-full` already use for "this control is the live one" — is reserved for a
     control that opens a reader in place. Named for the behaviour rather than for the tab, because
     the mark has to mean the same thing wherever it is drawn.

     ONE VALUE IN BOTH THEMES, and that is deliberate rather than lazy: --blue-fill is #0b62c4 on
     dark and on light, so #fff measures 5.98:1 on it either way (--blue-fill exists precisely
     because #fff on --blue is 3.03:1). A theme-conditional fill would need two contrast proofs and
     would drift; this needs one. --act-open-hi is the hover, checked the same way: #fff on
     #1272d8 is 4.79:1, still AA at the 12px this chip is set in. */
  --act-open: var(--blue-fill);
  --act-open-hi: #1272d8;
  --act-open-ink: #ffffff;
  --amber: #f9ab00;
  --surface-elev: #1c1d22;
  --shadow-elev: 0 48px 110px -32px rgba(0,0,0,0.85), 0 12px 32px -14px rgba(0,0,0,0.6);
  --shadow-modal: 0 60px 140px -40px rgba(0,0,0,0.9), 0 16px 40px -18px rgba(0,0,0,0.65);
  --green: #5bd18b;
  --disp: "Google Sans Flex", "Google Sans", -apple-system, sans-serif;
  --sans: "Inter", -apple-system, "Helvetica Neue", sans-serif;
  --mono: "Google Sans Code", "Roboto Mono", Menlo, monospace;
}

/* ── LIGHT THEME ───────────────────────────────────────────────────────────────────────────────
   LA: "add a theme button to manually change in case."

   Dark stays the default and the shipped identity; this is a manual alternative, set by
   `data-theme="light"` on <html> and remembered. Only the TOKENS are redefined — not one rule
   below this block knows which theme is active, which is the whole reason the token layer exists.

   Three things are not simple inversions, and getting them wrong is what makes a bolted-on light
   theme look bolted on:

   1. `--white` is not "white", it is STRONGEST INK. On dark that is #fff; on light it has to be
      near-black, or every heading on the site vanishes. Same for --bg-deep, which is "further
      back" and therefore DARKER on dark and LIGHTER on light only in the sense of receding.
   2. The blues had to change value, not just role. #2e96ff on white is 2.6:1 — it fails as text
      everywhere it is currently used as text. Light gets a darker blue for ink and keeps a bright
      one only for fills.
   3. The surface ORDER inverts. On dark, surfaces get lighter as they come forward; on light they
      get whiter as they come forward and the page ground is the grey. Keeping the dark ordering
      would put a dark panel on a white page. */
:root[data-theme="light"] {
  --bg: #f5f5f7;
  --bg-deep: #ececef;
  --surface: #ffffff;
  --surface-2: #fafafa;
  --surface-3: #f0f0f2;
  --surface-elev: #ffffff;
  --border: rgba(0,0,0,0.11);
  --border-strong: rgba(0,0,0,0.19);

  --text: #1d1d1f;
  --white: #0b0b0d;          /* strongest ink — see note 1 */
  --text-2: #55595f;         /* 7.1:1 on --surface */
  --text-3: #63686e;         /* 5.4:1 on --surface. #6b7076 measured 4.24:1 at one 11px
                                call site on --surface-3, which is the ground that matters. */

  /* The light counterpart the chip surface never had: ink-tinted, not white-tinted. */
  --dgm-chip: rgba(0,0,0,0.045);
  --dgm-chip-hi: rgba(0,0,0,0.075);

  --blue: #0b62c4;           /* 5.9:1 on white — usable as text, unlike #2e96ff at 2.6:1 */
  --blue-bright: #0a4fa0;    /* links and hovers go DARKER on light, not brighter */
  --blue-fill: #0b62c4;
  --blue-soft: rgba(11,98,196,0.09);
  --blue-border: rgba(11,98,196,0.30);

  --green: #17794c;
  --amber: #8a5a00;
  --red: #b3261e;
  --red-deep: #f3d6d4;
  --purple: #6d3bbf;
  --green-soft: rgba(23,121,76,.10);
  --amber-soft: rgba(138,90,0,.10);
  --red-soft: rgba(179,38,30,.09);

  --scrim: rgba(28,28,32,.55);
  --shadow-elev: 0 24px 60px -24px rgba(0,0,0,0.20), 0 6px 18px -10px rgba(0,0,0,0.12);
  --shadow-modal: 0 40px 100px -32px rgba(0,0,0,0.28), 0 10px 28px -14px rgba(0,0,0,0.16);
}
/* The semantic layer is derived, so it has to be re-derived — otherwise a light page keeps the
   dark theme's signal colours and "one colour, one meaning" quietly breaks at the theme boundary. */
:root[data-theme="light"] {
  --sig-good: var(--green);   --sig-good-soft: var(--green-soft);
  --sig-warn: var(--amber);   --sig-warn-soft: var(--amber-soft);
  --sig-bad:  var(--red);     --sig-bad-soft:  var(--red-soft);
  --sig-info: var(--blue);    --sig-note: var(--text-2);
}
/* Media wells are painted near-black across the site so a photograph never lands on a bright
   rectangle while it loads. On a light page that reads as a hole, so the well follows the theme. */
:root[data-theme="light"] .card-media,
:root[data-theme="light"] .plate-card img,
:root[data-theme="light"] .side img { background: var(--surface-3); }

/* The toggle itself. Same pill as the rest of the chrome, in the topbar. */
/* THE THEME SWITCH IS AN ICON BESIDE THE MARK, NOT A PILL IN THE CORNER.
   It was `☾ DARK` in an outlined lozenge with `margin-left: auto`, which parked it at the far
   right of the bar — the loudest object on every page of the site, naming a state nobody had
   asked about, as far from the identity as the bar allows. It read as a section rather than a
   control. A theme preference belongs against the mark it re-colours.
   NO BORDER AND NO GROUND until you point at it: an always-outlined 36px lozenge next to a
   wordmark competes with it. The affordance arrives on hover and focus, which is where it is
   needed, and the glyph alone carries it the rest of the time. */
.theme-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; margin: 0;
  border: 1px solid transparent; border-radius: var(--r-pill);
  background: transparent; color: var(--text-3);
  font-size: 15px; line-height: 1; cursor: pointer;
}
.theme-btn:hover { color: var(--text); border-color: var(--border); background: var(--surface-elev); }
.theme-btn:focus-visible { color: var(--text); border-color: var(--text-3); background: var(--surface-elev); }
/* 32px clears the 24px WCAG 2.5.8 minimum with room, but a thumb wants 44 — and the icon must not
   grow to get it, or it stops being a quiet mark beside the wordmark. The box expands, not the
   glyph. */
@media (pointer: coarse) {
  .theme-btn { position: relative; }
  .theme-btn::after { content: ""; position: absolute; inset: -6px; border-radius: inherit; }
}
/* The button is injected by theme.js as the last child, so `order` is what puts it beside the
   wordmark rather than a DOM move — one declaration, and no script has to know about layout. */
/* `.topbar-in` is `justify-content: space-between`, which is what actually pinned the switch to
   the far corner — removing its `margin-left: auto` changed nothing, and the button stayed 906px
   from the mark. Ordering it second and giving it `margin-right: auto` makes the pair hug the
   left together, while any third child a tab injects still goes to the right where it belongs. */
.topbar-in .wordmark { order: 1; }
.topbar-in .theme-btn { order: 2; margin-right: auto; }
/* ON A PHONE THE ICON GOES IN FRONT OF THE MARK, NOT AFTER IT.
   The home tab's wordmark is not the same width as everyone else's — app.js appends a "what's
   new" pill into it, taking it to 338px. At 390 the bar has 350px of room, so mark + gap + icon
   is 382 and the icon wrapped onto a second line, sitting alone under the mark at y:50 while
   every other tab kept it beside. LA's own first framing was "an icon on the left of / or after"
   the wordmark; after is right when there is room and left is right when there is not, because
   a 32px icon leading the line always fits and the mark can then take whatever remains. */
/* THE ICON WRAPPED BECAUSE THE LINE IS 32px TOO SHORT, so the fix is the 32px, not the order.
   Measured at 390: the bar has 350px between its paddings, and the home tab's wordmark — which
   carries a "what's new" pill the other tabs do not — is 338. Add the 12px gap and the 32px icon
   and it is 382. Reordering the icon in front only moved the wrap from below the mark to above
   it; the arithmetic was unchanged.
   Dropping the mark to 11px takes it to ~296 and the row closes at ~336. 11.5px was not
   enough: it landed on 350 against exactly 350 of room, and equal is still a wrap. The pill shrinks with
   it because it inherits, and at this size the mark is still the second-largest thing in a bar
   whose job is not to shout its own name.

   SCOPED TO `.topbar-in .wordmark`, NOT `.wordmark`, and that is not cosmetic: this block sits
   above `.wordmark { font-size: 13px }`, so a bare-class rule loses on document order and the
   mark stayed 338px wide with the icon still wrapped. Two selectors beat one wherever it sits.
   This file already records the colour and the text-decoration versions of the same trap.

   And the version of this note that fixed the specificity SHIPPED BROKEN for one measurement
   round, because I opened it after the previous comment had already closed: the prose became raw
   CSS, the parser discarded tokens until it could recover, and it took the media query with it.
   The measurement said 13px and I nearly went looking for a cascade problem that did not exist.
   Every rule in this file is a rule somebody could not see. */
@media (max-width: 430px) {
  .topbar-in .wordmark { font-size: 11px; }
  .topbar-in { gap: var(--s-1, 8px); }
}

* { margin: 0; padding: 0; box-sizing: border-box; }
[hidden] { display: none !important; }
html { scroll-behavior: smooth; }
body {
  font-family: var(--sans);
  background: var(--bg);
  color: var(--text);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  letter-spacing: -0.006em;
}
img { display: block; max-width: 100%; }
button, input, select { font: inherit; color: inherit; }
button { font-family: inherit; cursor: pointer; }

/* ── Gate ─────────────────────────────────────────────── */
.gate {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center;
  background:
    radial-gradient(1100px 560px at 72% -10%, var(--blue-soft), transparent 62%),
    radial-gradient(820px 480px at 8% 112%, rgba(46,150,255,0.06), transparent 60%),
    var(--bg);
  padding: var(--s-6);
}
.gate-card { width: 100%; max-width: 400px; text-align: center; }
.gate-mark {
  font-family: var(--mono); font-size: 14px; letter-spacing: 0.32em;
  color: var(--text-2); text-transform: uppercase; margin-bottom: var(--s-5);
}
.gate-mark span { color: var(--blue); }
.gate-title { font-family: var(--disp); font-size: 32px; font-weight: 500; letter-spacing: -0.02em; color: var(--white); }
.gate-sub { color: var(--text-2); font-size: 14px; margin: var(--s-2) 0 var(--s-6); }
.gate input {
  width: 100%; padding: var(--s-3) var(--s-4); border: 1px solid var(--border-strong);
  border-radius: var(--r-s); background: var(--surface); outline: none; text-align: center;
  font-family: var(--mono); font-size: 15px;
}
.gate input:focus { border-color: var(--blue-border); }
.gate input::placeholder { color: var(--text-3); }
.gate button {
  margin-top: var(--s-3); width: 100%; padding: var(--s-3);
  border: none; border-radius: var(--r-s); background: var(--blue); color: #fff; font-weight: 500;
}
.gate button:hover { background: var(--blue-bright); }

/* ── THE PINNED CHROME — THE ONLY PLACE THAT DECIDES WHAT STICKS ───────────────────────────────
   THIS BLOCK OWNS EVERY `position: sticky` IN THE PAGE CHROME. Nothing else in this stylesheet and
   nothing in site/tools/build-v5-shell.mjs may pin a band to the top of the viewport. If you are
   adding one, add it here, to the stack below, with its own `top` — do not write a second rule
   somewhere closer to the thing you are working on.

   WHY THE RULE HAD TO BECOME A BLOCK. `.topbar`, `.tabs` and `.bar` are siblings inside `#app`, and
   all three resolved to `position: sticky; top: 0; z-index: 40` — from FOUR rules in TWO files,
   each carrying a comment that claimed to be the only sticky element on the page:
     · `.bar`    below, in the control-band block
     · `.tabs`   below, in the v2-tabs block
     · `.tabs`   again, inside `@media (max-width: 760px)`
     · `.topbar` in the CSS string appended by build-v5-shell.mjs
   Same top, same z-index, so the last of the three in DOM order — `nav.bar` — painted over the
   other two. Measured at scrollY 3000 on the corpus tab: `document.elementFromPoint()` over the
   centre of the ACTIVE tab returned `span.dl-file`, a child of the bar. The tab was not merely
   hidden, it was not clickable — and all three tab links, both wordmark links and the theme
   control stayed focusable while visually obscured, which is WCAG 2.2 SC 2.4.11 Focus Not
   Obscured, a Level AA failure. The corpus tab is 280,611px tall at 375, so the only route to
   another tab was to scroll the whole way back to the top: LA's original complaint, "can't go back
   once I am there", returned by the comment that claimed to have fixed it. Four rules, each
   locally correct, none of them able to see the other three. That is why there is now one.

   WHAT IS PINNED, AND WHAT IS NOT:
   · `.tabs` — PINNED at top: 0. It is the go-back affordance and the entire reason anything here
     is sticky. Being topmost, it is also what sits under the notch in landscape.
   · `.bar` — PINNED at top: var(--tabs-h), immediately under the strip. It carries the search, the
     filters, the segmented control and the download: the controls a reader operates WHILE
     scrolling, which is what earns a band its place on a screen.
   · `.topbar` — NOT PINNED. It scrolls away with the page, at every width. It holds the wordmark
     and the theme control — an identity and a preference, neither of them navigation, neither
     something a reader reaches for mid-page. Measured: 64px tall, so pinning it costs 64 + 51 =
     115px at 1440 before a single control that acts on the page, and 64 + 48 + 117 = 229px of a
     667px phone, a third of the screen, to keep a wordmark on it. The earlier comment for that
     rule argued the tab strip was "reachable by going back up"; on a 280,000px page it is not,
     which is the defect above, so the strip is the one that stays and the bar above it goes.

   THE OFFSETS ARE DISTINCT AND THE Z-INDICES ARE NOT EQUAL. Two bands at top: 0 sharing a z-index
   IS the defect; the stack is declared once, here, in numbers that cannot both be zero.

   `--tabs-h` IS THE STRIP'S MEASURED HEIGHT, NOT A GUESS: 46px at ≤360, 48px from 361 to 620,
   51px from 621 up. Those two steps are exactly the widths at which the tab font-size drops — and
   THOSE RULES ARE NOT IN THIS FILE, they are in the CSS tail appended by
   site/tools/build-v5-shell.mjs, which is why the three numbers here are stated with their
   breakpoints rather than left implicit. It is applied to the strip as
   `min-height` as well as to the bar as `top`, so the number cannot quietly become larger than the
   thing it offsets — and check-v5-tabs.mjs asserts, at all 18 widths and on all three tabs, that
   `.bar`'s pinned top equals `.tabs`'s pinned bottom and that nothing else is pinned at all. If a
   future edit changes the strip's height and not this number, that check fails instead of the
   navigation. */
:root { --tabs-h: 51px; }
@media (max-width: 620px) { :root { --tabs-h: 48px; } }
@media (max-width: 360px) { :root { --tabs-h: 46px; } }

.topbar { position: static; }
.tabs   { position: sticky; top: 0;             z-index: 41; min-height: var(--tabs-h); }
.bar    { position: sticky; top: var(--tabs-h); z-index: 40; }

/* THE PINNED SURFACES GO TRANSLUCENT AT ≥768px. LA, on an iPad, dark theme, templates tab
   mid-scroll: "is that on top normal? maybe the bar should be transparent too. This looks
   broken." A row of cards cut dead at an opaque band's bottom edge reads as a defect; the same
   row dimming through the band reads as chrome, and the blur is what says the veil is deliberate.

   THE ALPHA IS PER-THEME AND MEASURED, NOT TASTED. A translucent band's real ground is whatever
   scrolls beneath it, so AA is computed against the worst content each theme can carry under its
   chrome: pure white under the dark theme's, pure black under the light theme's. The weakest ink
   sitting directly on either band is --text-3 (pill keys, ≤12px, so the floor is 4.5:1):
     dark  92% → worst 4.87:1  (--text-3 on the bar, white beneath)
     light 98% → worst 4.57:1  (--text-3 on the strip, black beneath)
   Light gets almost no translucency because it CANNOT have more: a light veil over black content
   drowns its own grey ink, and blur is atmosphere, not a contrast substitute — 97.5% is the AA
   floor there, 98% is the margin. check-v5-tabs.mjs folds the same two backstops, so an alpha
   that breaks this arithmetic fails the gate rather than the reader.

   PHONES KEEP THE OPAQUE BASE SURFACES. The site-wide performance rule is backdrop-filter at
   768px+ only; below that nothing here applies and the base .tabs/.bar rules elsewhere in this
   file already paint solid token grounds. The hairline border-bottom each band carries is
   untouched: translucent or not, the chrome still terminates on an edge. */
:root { --chrome-a: 92%; }
:root[data-theme="light"] { --chrome-a: 98%; }
/* THE SELECTORS ARE DOUBLED ON PURPOSE. This block sits near the top of the file and the bands'
   opaque surface rules sit far below it at the same specificity, so a plain `.tabs { background }`
   here loses the cascade to its own fallback and the chrome silently stays opaque — which is
   exactly how the first draft of this rule shipped nothing: the sabotage run (--chrome-a at 52%)
   still measured an opaque bar. `.tabs.tabs` outweighs any later single-class rule without
   touching the surface blocks, which keeps the whole translucency decision in this one place. */
@media (min-width: 768px) {
  .tabs.tabs {
    background: color-mix(in srgb, var(--bg-deep) var(--chrome-a), transparent);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
  }
  /* The bar's opaque ground sits one step above the strip's — color-mix(--bg-deep 80%, --bg),
     declared in THE CONTROL BAND below. The translucent version keeps that same step. */
  .bar.bar {
    background: color-mix(in srgb, color-mix(in srgb, var(--bg-deep) 80%, var(--bg)) var(--chrome-a), transparent);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
  }
}

/* ── Topbar ───────────────────────────────────────────── */
/* RESERVE THE HEIGHT TWO SCRIPTS ARE ABOUT TO ADD. The bar paints at 50px, theme.js appends the
   theme control at ~840ms taking it to 65px, and app.js appends the "what's new" pill at ~2310ms
   taking it to 96px — so the entire viewport is shoved down 46px in two steps after first paint.
   Measured cost on the landing tab: CLS 0.458, where 0.1 is the threshold. Reserving the final
   height here, in the stylesheet rather than at runtime, means the space exists from parse time and
   the controls drop into it. Measured 0.458 -> 0.145 when injected late; declared it should reach
   near zero. If a control is ever added to this bar, this number moves with it. */
/* NO BORDER HERE. The topbar and the tab strip both used to pin at top:0 and both carried a
   1px bottom border, so scrolling parked them one under the other and the page showed two
   hairlines a few pixels apart. LA: "there remain a double line when I scroll." The topbar does
   not pin any more (see THE PINNED CHROME above) so that pairing cannot recur, but the border
   stays off: the tab strip's is the one that separates chrome from content, and this bar is
   already distinguished by sitting above it. */
.topbar { background: var(--bg-deep); min-height: 96px; }
.topbar-in {
  max-width: 1280px; margin: 0 auto; padding: 14px clamp(20px, 4vw, 48px);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); flex-wrap: wrap;
}
.wordmark { font-family: var(--mono); font-size: 13px; letter-spacing: 0.01em; white-space: nowrap; }
.wm-a { color: var(--text-2); }
.wm-sep { color: var(--text-3); margin: 0 var(--s-05); }
.wm-b { color: var(--blue); }
/* THE METHOD PILL WAS HERE, AND IT IS A TAB NOW. The rule that stood here styled a pill in the
   topbar, under a comment arguing that /method must not be a tab: check-v5-tabs held every TAB to
   the full control grammar — a download-first .ctl bar, a manifest placeholder, identical bar
   geometry — and /method hands over no dataset, so a tab faking a download to satisfy its own gate
   would be a defect wearing a feature's clothes.

   That reasoning was about the gate, not about the reader. The gate now carries two page classes
   (site/tools/tabs.mjs `DATA_TABS`): a data tab must draw the control bar, a page tab must draw
   none, and BOTH are asserted — so the exemption is a rule instead of a hole. Method is the fourth
   tab, build-v5-shell strips any surviving pill, and the shell and the tab sweep both fail on one.
   The rule is deleted rather than left unused: a stylesheet that describes a component the site no
   longer has is how the next reader concludes it still does. */
.topbar-meta { display: flex; gap: var(--s-3); flex-wrap: wrap; }
.topbar-meta span {
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.08em; color: var(--text-3);
}
.topbar-meta b { color: var(--text-2); font-weight: 500; }

/* ── Hero ─────────────────────────────────────────────── */
.hero {
  max-width: 1280px; margin: 0 auto;
  padding: clamp(36px, 6vh, 72px) clamp(20px, 4vw, 48px) 10px;
}
.hero h1 {
  font-family: var(--disp); font-weight: 500;
  font-size: clamp(30px, 4.6vw, 48px);
  letter-spacing: -0.025em; line-height: 1.08; color: var(--white);
}
.hero-sub { max-width: 620px; margin-top: var(--s-3); color: var(--text-2); font-size: 15px; }

/* ── Filter bar ───────────────────────────────────────── */
/* ── THE CONTROL BAND ──────────────────────────────────────────────────────────────────────────
   LA: "the controls are not vertically centred in the control bar's band."

   THE CAUSE WAS `margin-top: var(--s-5)`, AND NO AMOUNT OF PADDING WOULD HAVE FOUND IT.
   `.bar` painted `color-mix(--bg 86%, transparent)` over a `--bg` page — the same colour — so the
   20px margin above it was INSIDE the strip a reader perceives, which runs from the tab strip's
   bottom border (the last edge above) to `.bar`'s own (the first edge below). Measured across
   three tabs × six widths × two themes: 29px above the controls, 10px below, 36 of 36 off.
   `tools/check-v5-band.mjs` now measures that painted span rather than `.ctl`'s box — the first
   instrument I wrote measured `.ctl` inside `.bar` and reported a 1px skew on every single case,
   because the box the code calls the band is not the band anybody sees.

   AND THE BAND IS CHROME, SO IT SITS UNDER THE PAGE RATHER THAN ON IT. Three surfaces, three
   relationships: the tab strip is deepest, this band is one step above it and still BELOW the
   content ground, and the page is the ground. Before, all three landed within a couple of
   luminance values of each other and only a hairline separated them, which is why the whole top
   of the site read as one flat slab with lines drawn across it.

   THE OPAQUE GROUND BELOW IS NOW THE PHONE'S SURFACE AND THE TABLET'S FALLBACK. Blur and
   translucency left this rule in the flat-slab pass, then came back at ≥768px only — LA, mid-
   scroll on an iPad: a card row cut dead at an opaque band's edge "looks broken". The translucent
   version lives in THE PINNED CHROME near the top of this file, derived from the same mix at a
   measured per-theme alpha; under 768px this solid ground is all there is, which is the site's
   performance rule (backdrop-filter 768px+ only). The theme still follows the tokens — the
   light-theme failure this rule was written for cannot come back, because nothing here is a
   literal colour. */
/* THE PIN IS NOT HERE. `position: sticky; top: 0; z-index: 40` used to sit in this rule and it was
   one of four such rules; the band's offset and stacking are declared once, in THE PINNED CHROME
   near the top of this file. Everything below is the band's SURFACE, which is what this block is
   about. */
.bar {
  background: color-mix(in srgb, var(--bg-deep) 80%, var(--bg));
  border-bottom: 1px solid var(--border);
  margin-top: 0;
}
.bar-row {
  max-width: 1280px; margin: 0 auto;
  padding: 10px clamp(20px, 4vw, 48px);
  display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap;
}
/* The filters take the row; the count is pushed to the far end so it reads as a result, not a
   label sitting in front of the controls that produce it. */
.bar-filters { display: flex; gap: var(--s-1); flex-wrap: wrap; }
.bar-filters + .hit-count { margin-left: auto; }
.search {
  flex: 1 1 220px; min-width: 150px;
  padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-s);
  background: var(--surface); outline: none;
}
.search:focus { border-color: var(--blue-border); }
.search::placeholder { color: var(--text-3); }
.bar select {
  padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-s);
  background: var(--surface); cursor: pointer; max-width: 44vw;
  -webkit-appearance: none; appearance: none;
}
.seg { display: flex; height: 36px; border: 1px solid var(--border); border-radius: var(--r-s); overflow: hidden; background: var(--surface); }
.seg button { padding: 0 var(--s-3); border: none; background: transparent; color: var(--text-2); font-size: 14px; }
.seg button.on { background: var(--blue-soft); color: var(--blue-bright); }

/* ── Sections ─────────────────────────────────────────── */
main { max-width: 1280px; margin: 0 auto; padding: 4px clamp(20px, 4vw, 48px) 80px; }
.genre { padding-top: var(--s-10); }
.genre-head { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.genre-head h2 {
  font-family: var(--disp); font-weight: 500;
  font-size: clamp(20px, 2.4vw, 27px); letter-spacing: -0.015em; color: var(--white);
}
.genre-head .n { font-family: var(--mono); color: var(--text-3); font-size: 12px; }

/* ── List view (default) ──────────────────────────────── */
.rows { margin-top: var(--s-3); border: 1px solid var(--border); border-radius: var(--r-m); overflow: hidden; }
.row {
  display: grid; align-items: baseline; column-gap: var(--s-3);
  grid-template-columns: minmax(150px, 190px) 1fr auto auto;
  padding: var(--s-3) var(--s-4);
  background: var(--surface); border-top: 1px solid var(--border);
  cursor: pointer; text-align: left; width: 100%; border-left: none; border-right: none; border-bottom: none;
  font-size: 14px;
}
.row:first-child { border-top: none; }
.row:hover { background: var(--surface-2); }
.row-name { font-family: var(--disp); font-weight: 500; color: var(--white); font-size: 15px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row-name .anchor { color: var(--amber); font-size: 12px; margin-left: var(--s-1); }
.row-why { color: var(--text-2); overflow: hidden; white-space: nowrap; text-overflow: ellipsis; min-width: 0; }
.row-dim { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; color: var(--blue-bright); white-space: nowrap; }
.row-meta { font-family: var(--mono); font-size: 11px; color: var(--text-3); white-space: nowrap; }
.row-meta .dark-dot { color: var(--text-2); }

/* ── Cards view ───────────────────────────────────────── */
.grid {
  margin-top: var(--s-3);
  display: grid; gap: var(--s-4);
  grid-template-columns: repeat(auto-fill, minmax(min(330px, 100%), 1fr));
}
/* ── THE CARD IS A MOUNT AND THE CAPTURE IS A PRINT LAID ON IT ─────────────────────────────────
   LA, on the gradient that used to end each thumbnail: he disliked it, and he was right — see the
   note on the mask rule further down for what it actually did to 739 photographs.

   The mount is the answer to the problem that fade was reaching for. A crop through the middle of
   someone's page looks broken when nothing frames it and intentional when something does; a fade
   makes the crop foggy, a mount makes it a decision. 4px of the card's own surface shows on all
   four sides of every capture, so the picture is an OBJECT PLACED ON A BOARD rather than the
   card's own skin — which is the "detaching the object from the surface" LA asked for, said in the
   one place it is said 739 times.

   THE RADII ARE CONCENTRIC AND THAT IS THE WHOLE TRICK. outer = inner + inset, so 4px of mount
   under a 12px print gives a 16px card: --r-l over --r-m over --s-1. Get this wrong by even 2px
   and the corners bulge and the inset reads as a misalignment rather than as a margin. This is
   also why the radius ladder gains no new value — the three rungs it already had are exactly the
   three this needs.

   ── HOW IT LEAVES THE SURFACE, AND WHY THE TWO THEMES DO NOT GET THE SAME ANSWER ──
   On #121317 a drop shadow is invisible: there is nothing darker for it to be, so a shadow on a
   dark ground is a blurred smudge that costs a paint and buys nothing. What a raised object
   actually does under light from above is CATCH ITS TOP EDGE — so the dark theme lifts with a 1px
   highlight and a surface one step lighter than the page, which is how a physical control panel
   reads and how every well-made dark interface does it. The light theme gets the shadow, because
   there the physics work and a white card on a grey page has no lighter edge to catch. Same
   object, two grounds, two mechanisms. */
.card {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-l);
  padding: var(--s-1);
  overflow: hidden; cursor: pointer;
  box-shadow: inset 0 1px 0 0 color-mix(in srgb, var(--white) 7%, transparent);
  /* Named properties only — `box-shadow` is deliberately NOT in this list. Animating a shadow
     repaints the card and its ground on every frame of a 250ms transform, on up to 739 of them. */
  transition: border-color .2s, transform .25s cubic-bezier(.2,.7,.2,1);
}
:root[data-theme="light"] .card {
  box-shadow: 0 1px 2px rgba(0,0,0,.05), 0 10px 24px -14px rgba(0,0,0,.16);
}
@media (hover: hover) {
  .card:hover { transform: translateY(-3px); border-color: var(--border-strong); }
  .card:hover .card-visit { opacity: 1; }
}
.card-media { position: relative; aspect-ratio: 16 / 10; background: var(--surface-3); overflow: hidden;
  border-radius: var(--r-m); }
.card-media img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
/* THE PLACEHOLDER SHIMMER WAS THE MOST EXPENSIVE THING ON THIS SITE, AND IT DECORATED NOTHING.
   It animated `background-position` — a paint property, on the main thread, every frame — on every
   card at once. A performance audit measured 712 concurrent infinite animations on the landing tab
   three seconds after load, the main thread saturated at 5.13s of work per 5s of doing nothing, a
   median frame of 33ms (30fps) sitting still, and 44.2% of frames dropped across a full scroll of
   the 127-screen page. With it removed: 60fps at rest and 0.1% dropped on the same scroll.

   It also drove the style-recalc cost, because `:has(img.ld)` re-invalidates as each of 739 images
   loads — +2,386 recalcs and +1.59s of recalc time on one scroll.

   Kept as a shimmer, moved onto the compositor: the gradient now rides a pseudo-element under
   `transform: translateX()`, which never touches paint or layout, and the loaded state hides the
   pseudo-element instead of switching an animation off. The card looks the same and the frame
   budget comes back. */
.card-media {
  background: var(--surface);
  isolation: isolate;
  /* THE PRINT ALWAYS HAS AN EDGE, WHATEVER IS IN IT — and it has to be an INSET ring, not a
     border. 739 captures include white pages, near-black pages and full-bleed photography; a
     border sits outside the picture, so a white capture on a white card swallows it and a black
     capture on a dark card swallows it too. Drawn over the image, the edge survives every one of
     them. Mixed from `--text` so it inverts with the theme by construction rather than by a
     second rule somebody has to remember to update. */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text) 12%, transparent);
}
.card-media::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: linear-gradient(110deg, var(--surface) 30%, var(--surface-2) 45%, var(--surface) 60%);
  background-size: 55% 100%;
  background-repeat: repeat-x;
  animation: shimmer 1.5s linear infinite;
}
.card-media:has(img.ld)::before, .card-media:has(.noshot)::before { content: none; }
.card-media img { position: relative; z-index: 1; opacity: 0; transition: opacity .4s ease; }
.card-media img.ld { opacity: 1; }
@keyframes shimmer { from { transform: translate3d(-55%, 0, 0); } to { transform: translate3d(0, 0, 0); } }
/* A placeholder that animates is decoration; a reader who has asked for less motion gets the
   static ground instead, which is what the rest of this stylesheet already does. */
@media (prefers-reduced-motion: reduce) {
  .card-media::before { animation: none; }
}
.genre { animation: fadeUp .4s ease both; }
@keyframes fadeUp { from { opacity: 0; transform: translateY(10px); } }

.card-media .noshot {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--text-3); font-family: var(--mono); font-size: 12px;
}
/* AN OVERLAY ON A PHOTOGRAPH DOES NOT FOLLOW THE PAGE THEME. This chip sits on top of a site
   capture, so its ground is the picture, not the page — and its ink has to be legible against a
   photograph in either theme. It took `--blue-bright` from the palette, which is a bright blue on
   dark and a DARK blue on light, so in the light theme it became dark ink on a dark scrim: 2.41:1,
   measured on 777 cards. Both the scrim and the ink are stated here and stay stated.
   Same rule the components already needed for text laid over their own media. */
.card-visit {
  position: absolute; right: 10px; bottom: 10px; opacity: 0; transition: opacity .2s;
  font-family: var(--mono); font-size: 11px; padding: var(--s-1) var(--s-3);
  background: rgba(14,15,18,.85); color: #b9d9ff; border-radius: var(--r-pill);
  border: 1px solid rgba(46,150,255,0.42);
}
/* THE PADDING A READER SEES IS UNCHANGED, and that is deliberate: the mount takes its 4px out of
   the body's own inset rather than out of the card, so the copy sits exactly where it sat before
   (16px from the card's edge, 12px under the print) and only the picture moved. A material change
   that also reflows every line of text is two changes, and the second one nobody asked for. */
.card-body { padding: var(--s-3); }
.card-top { display: flex; align-items: baseline; gap: var(--s-2); }
.card-top h3 { font-family: var(--disp); font-size: 15px; font-weight: 500; color: var(--white); }
.card-top .origin { font-family: var(--mono); color: var(--text-3); font-size: 11px; }
.card-top .anchor { color: var(--amber); font-size: 12px; }
.card-why {
  margin-top: var(--s-1); color: var(--text-2); font-size: 13px;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.card-chips { margin-top: var(--s-2); display: flex; gap: var(--s-1); flex-wrap: wrap; }
/* A CHIP THAT NAVIGATES MUST LOOK LIKE IT DOES. The Taxonomies chips became links to the filtered
   Design tab, and an <a> inherits none of a chip's affordance: without this it reads as the same
   inert label it was, which is the half of the complaint that a working href does not fix. Given
   the pointer, the underline-on-hover and the focus ring every other link on this site has, plus a
   dimmed count so the number reads as a quantity rather than as part of the name. */
.chip-link { display: inline-flex; align-items: baseline; gap: .5ch; text-decoration: none; cursor: pointer;
  transition: border-color .15s, color .15s, background-color .15s; }
.chip-link:hover { color: var(--text); border-color: var(--border-strong); background: var(--surface-2); }
.chip-link:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.chip-n { color: var(--text-3); font-variant-numeric: tabular-nums; }
.chip-link:hover .chip-n { color: var(--text-2); }
.tx-cell-link { color: inherit; text-decoration: none; border-bottom: 1px solid transparent; }
.tx-cell-link:hover { border-bottom-color: currentColor; }
.tx-cell-link:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

.chip {
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.05em; text-transform: uppercase;
  padding: var(--s-05) var(--s-2); border-radius: var(--r-pill); border: 1px solid var(--border); color: var(--text-2);
  /* STATE A SURFACE OR THE BROWSER WILL. `.chip` set a border and a colour and no background, which
     is invisible on a <span> and catastrophic on a <button>: the Components filters are
     `<button class="chip">`, so all 28 unselected ones rendered in the user agent's ButtonFace —
     rgb(239,239,239), a light grey slab on a near-black page. LA: "those labels make no sense."
     Same defect class as the links that fell back to #0000EE: styled for colour, never given a
     surface. */
  background: transparent;
}
.chip.dim { background: var(--blue-soft); border-color: var(--blue-border); color: var(--blue-bright); }

/* ON A CARD, ONE CHIP CARRIES A CLAIM AND THE OTHERS CARRY COUNTS — and they were rendering
   byte-identically at 11px/400/#6BB6FF on the same wash. So on a described-only card the loudest
   thing in the body was "7 OBSERVED", a number nobody filters on, brighter than the site's own
   name. The distinction a training team actually needs — does this record carry code — was set at
   the same volume as a tally. Scoped to the card so `.chip.dim` keeps its meaning elsewhere. */
.card-chips .chip.dim { background: transparent; border-color: var(--border); color: var(--text-3); }
.card-chips .chip-full { background: var(--blue-fill); border-color: var(--blue-fill);
  color: #fff; font-weight: 600; }

/* The "whole page in react" chip is a LINK to that page, so it has to behave like one: a pointer,
   a hover, a focus ring, and the arrow that every other outbound control on this site carries.
   Previously it looked exactly like the two inert labels beside it while being the only thing on
   the card that leads anywhere. */
.card-chips a.chip-go { text-decoration: none; cursor: pointer; display: inline-flex;
  /* NO box-shadow IN A TRANSITION. Animating box-shadow repaints the element every frame and is
     the one property this project's performance rule names outright. Nothing here even sets a
     shadow on hover or focus, so the declaration bought a repaint path for an effect that does
     not exist. background and border-color carry the whole hover. */
  align-items: center; gap: .5ch; transition: background-color .15s, border-color .15s; }
.card-chips a.chip-go:hover { background: var(--blue); border-color: var(--blue); }
.card-chips a.chip-go:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 2px; }
.card-chips a.chip-go span { transition: transform .15s; }
.card-chips a.chip-go:hover span { transform: translate(1px, -1px); }
@media (prefers-reduced-motion: reduce) {
  .card-chips a.chip-go, .card-chips a.chip-go span { transition: none; }
}

/* A CHIP THAT IS A CONTROL, not a label. `.chip` does both jobs across this site, and only one of
   them needs a cursor, a hover, a target size and a selected state.

   SELECTED MUST BE THE LOUDEST THING IN THE ROW. It was the quietest: unselected chips came out in
   bright UA grey while the selected one got `--blue-soft`, a 14%-alpha wash — so the row read as
   "one disabled item among many active ones", the exact inverse of what it meant. Selected is now
   a filled blue with white type (--blue-fill exists precisely because #fff on --blue is 3.03:1). */
button.chip {
  cursor: pointer; background: var(--surface);
  min-height: 32px; padding: var(--s-1) var(--s-3);
  display: inline-flex; align-items: center;
}
button.chip:hover { color: var(--text); border-color: var(--text-3); background: var(--surface-elev); }
button.chip.on,
button.chip.on:hover { background: var(--blue-fill); border-color: var(--blue-fill); color: #fff; }

/* ── Modal ────────────────────────────────────────────── */
.modal { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; padding: var(--s-4); }
/* BACKDROP-FILTER IS DESKTOP-ONLY (768px+). A blur over the full viewport is the most expensive
   thing this page can composite, and on a phone it is paid for on the one device that can least
   afford it — behind an opaque scrim that already hides what is being blurred. The scrim colour
   carries the separation on its own below 768. */
.modal-scrim { position: absolute; inset: 0; background: var(--scrim); }
@media (min-width: 768px) {
  .modal-scrim { -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
}
/* ── THE THIRD RELATIONSHIP: ABOVE THE PAGE ────────────────────────────────────────────────────
   A card is ON the page, the control band is chrome UNDER it, and this is the one thing that is
   genuinely above both — and until now all three were separated by the same 1px border, which is
   why the site read as flat. The modal keeps every signal the other two only get one of: the
   strongest surface, the strong border, a real shadow in BOTH themes (here the scrim gives the
   shadow something to fall on, which is the reason a shadow works here and not on a card), and
   the same top-edge highlight the card uses so the two read as the same material at two heights. */
.modal-card {
  position: relative; background: var(--surface-elev); border: 1px solid var(--border-strong);
  border-radius: var(--r-l); overflow: auto;
  width: min(1060px, 100%); max-height: calc(100dvh - 36px);
  box-shadow: var(--shadow-modal), inset 0 1px 0 0 color-mix(in srgb, var(--white) 9%, transparent);
  animation: modalIn .22s ease both;
}
@keyframes modalIn { from { opacity: 0; transform: translateY(12px) scale(.985); } }
.modal-x {
  position: sticky; top: 0; float: right; margin: var(--s-3) var(--s-3) 0 0;
  width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--border-strong);
  background: var(--surface-3); color: var(--text-2); font-size: 19px; line-height: 1; z-index: 2;
  transition: color .15s, border-color .15s;
}
.modal-x:hover { color: var(--text); border-color: var(--blue-border); }
.modal-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-elev); border: 1px solid var(--border-strong); color: var(--text-2);
  box-shadow: 0 10px 28px -10px rgba(0,0,0,0.7);
  transition: color .15s, border-color .15s, transform .15s;
}
.modal-nav:hover { color: var(--white); border-color: var(--blue-border); transform: translateY(-50%) scale(1.06); }
.modal-nav.prev { left: max(18px, calc(50% - 530px - 66px)); }
.modal-nav.next { right: max(18px, calc(50% - 530px - 66px)); }
.modal-media { display: grid; grid-template-columns: 1fr auto; gap: var(--s-3); padding: var(--s-3) var(--s-3) 0; align-items: start; }
.modal-hero { border-radius: var(--r-s); border: 1px solid var(--border); width: 100%; background: var(--bg-deep); }
.modal-mobile { border-radius: var(--r-s); border: 1px solid var(--border); width: 150px; background: var(--bg-deep); }
.modal-body { padding: var(--s-2) var(--s-6) var(--s-8); }
.modal-kicker { font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-3); }
.modal-name { font-family: var(--disp); font-weight: 500; font-size: clamp(26px, 3.4vw, 38px); letter-spacing: -0.02em; color: var(--white); margin-top: var(--s-05); }
.modal-desc { color: var(--text-2); margin-top: var(--s-1); }
.modal-why { margin-top: var(--s-3); max-width: 640px; }
.modal-chips { margin-top: var(--s-4); display: flex; gap: var(--s-1); flex-wrap: wrap; }
.modal-visit {
  display: inline-block; margin-top: var(--s-5); padding: var(--s-3) var(--s-5); border-radius: var(--r-s);
  background: var(--blue); color: #fff; text-decoration: none; font-weight: 500;
}
.modal-visit:hover { background: var(--blue-bright); }
.empty { padding: var(--s-16) 0; text-align: center; color: var(--text-3); }

.foot { max-width: 1280px; margin: 0 auto; padding: 0 clamp(20px, 4vw, 48px) 60px; color: var(--text-3); font-size: 12px; font-family: var(--mono); }

/* ── Mobile ───────────────────────────────────────────── */
@media (max-width: 720px) {
  .topbar-meta { display: none; }
  /* The desktop band is already opaque and already blur-free, so the only thing this override still
     has to say is the safe area. It used to also repaint the bar as `--bg`, which on a phone put
     the band at the SAME value as the content ground and undid the one step of recession the whole
     chrome/content distinction rests on. */
  .bar { padding-top: env(safe-area-inset-top); }
  .bar-row { flex-wrap: wrap; row-gap: var(--s-2); padding-top: var(--s-3); padding-bottom: var(--s-3); }
  .search { flex: 1 1 110px; min-width: 110px; }
  .seg { flex: 0 0 auto; }
  .seg button, .search, .filters-btn { font-size: 13px; }
  .filters-btn { padding: var(--s-2) var(--s-2); }
  .exp-btn { padding: var(--s-2) var(--s-2); }
  .row { grid-template-columns: 1fr auto; row-gap: var(--s-05); }
  .row-why { display: none; }
  .row-meta { display: none; }
  .modal { padding: 0; align-items: flex-end; }
  .modal-card { border-radius: 16px 16px 0 0; max-height: 94dvh; border-left: none; border-right: none; border-bottom: none; }
  .modal-media { grid-template-columns: 1fr; }
  .modal-mobile { display: none; }
  .modal-body { padding: 52px 18px calc(88px + env(safe-area-inset-bottom)); }  /* 52px, not 4: the sticky close button floats right with a surface behind it and no gutter, so body text ran underneath it and a sentence was cut mid-word at 390. */
  .modal-nav { top: auto; bottom: calc(18px + env(safe-area-inset-bottom)); transform: none; }
  .modal-nav:hover { transform: scale(1.06); }
  .modal-nav.prev { left: calc(50% - 58px); }
  .modal-nav.next { right: calc(50% - 58px); }
  /* Touch hit area without touching the visual size: the compact chrome stays 36px
     tall by design, so grow the tappable box with a centred pseudo-element instead
     of padding the control. Keeps the bar's density, meets the 44px touch floor. */
  .filters-btn, .exp-btn, .icon-btn, .seg button, .wm-link {
    position: relative;
  }
  .filters-btn::after, .exp-btn::after, .icon-btn::after, .seg button::after, .wm-link::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    width: max(100%, 44px); height: max(100%, 44px);
  }
}
@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
  html { scroll-behavior: auto; }
  .card-media, .genre, .picker-panel, .modal-card { animation: none; }
  .card-media img { transition: none; opacity: 1; }
}

.exp-btn {
  height: 36px; padding: 0 var(--s-3); border: 1px solid var(--border); border-radius: var(--r-s);
  background: var(--surface); color: var(--text-2); font-size: 14px; cursor: pointer;
}
.exp-btn:hover { color: var(--blue-bright); border-color: var(--blue-border); }

/* ── Filtering v2: pills + picker ─────────────────────── */
:root { --exc: var(--amber); --exc-soft: rgba(249,171,0,0.12); --exc-border: rgba(249,171,0,0.4); }
.filters-btn {
  display: flex; align-items: center; gap: var(--s-2); flex: 0 0 auto;
  height: 36px; padding: 0 var(--s-3); border: 1px solid var(--border); border-radius: var(--r-s);
  background: var(--surface); color: var(--text-2); font-size: 14px;
}
.filters-btn:hover { color: var(--text); }
.filters-btn[aria-expanded="true"] { border-color: var(--blue-border); color: var(--blue-bright); }
.fbadge {
  min-width: 16px; height: 16px; border-radius: var(--r-pill); background: var(--blue);
  color: #fff; font-family: var(--mono); font-size: 11px; line-height: 16px;
  text-align: center; padding: 0 var(--s-1);
}
.pill-rail {
  flex: 1 1 0; min-width: 0; display: flex; gap: var(--s-1); align-items: center;
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 12px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 12px, #000 calc(100% - 12px), transparent);
}
.pill-rail::-webkit-scrollbar { display: none; }
.pill {
  display: inline-flex; align-items: center; flex: 0 0 auto;
  border: 1px solid var(--blue-border); border-radius: var(--r-pill); background: var(--blue-soft);
  transition: background-color .15s, border-color .15s;
}
.pill-body { display: inline-flex; align-items: center; gap: var(--s-1); border: none; background: transparent; padding: var(--s-1) var(--s-05) var(--s-1) var(--s-3); }
/* THE KEY AND THE DISMISS WERE --text-3, AND THAT FAILED BEFORE ANY TRANSLUCENCY. The pill's
   blue-soft fill darkens (light theme) or lightens (dark) whatever ground it sits on, and
   --text-3 on that compound measured 4.28:1 on the light theme's OPAQUE bar — an AA failure at
   12px that had been shipping, out of check-v5-tabs' sight because pills only exist once a
   filter is applied. No chrome alpha fixes it; even 100% is 4.28:1. The ink moves one step up:
   worst case with the translucent chrome is now 4.87:1 (dark theme, white content beneath). */
.pill-k { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-2); }
.pill-v { font-family: var(--mono); font-size: 11px; color: var(--blue-bright); white-space: nowrap; }
.pill-x { border: none; background: transparent; color: var(--text-2); font-size: 14px; padding: var(--s-1) var(--s-2) var(--s-1) var(--s-1); line-height: 1; }
.pill-x:hover { color: var(--text); }
.pill.exc { border-color: var(--exc-border); background: var(--exc-soft); }
.pill.exc .pill-v { color: var(--exc); text-decoration: line-through; }
.pill-clear {
  flex: 0 0 auto; border: none; background: transparent; color: var(--text-3);
  font-family: var(--mono); font-size: 11px; padding: var(--s-1) var(--s-2); text-decoration: underline;
}
.pill-clear:hover { color: var(--text); }
.hit-count { flex: 0 0 auto; font-family: var(--mono); font-size: 12px; color: var(--text-2); }
.icon-btn {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border: 1px solid var(--border); border-radius: var(--r-s);
  background: var(--surface); color: var(--text-2);
}
.icon-btn:hover { color: var(--blue-bright); border-color: var(--blue-border); }
.export { position: relative; flex: 0 0 auto; }
/* On phones the right-anchored 300px pop rendered mostly off-screen — every download
   link unreachable, and no overflow because negative-left does not scroll. Clamp it. */
.exp-pop { max-width: min(300px, calc(100vw - 24px));
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  display: flex; flex-direction: column; min-width: 110px;
  background: var(--surface-2); border: 1px solid var(--border-strong); border-radius: var(--r-s);
  overflow: hidden; box-shadow: 0 14px 34px -12px rgba(0,0,0,0.55);
}
.exp-pop button { border: none; background: transparent; color: var(--text-2); font-family: var(--mono); font-size: 12px; padding: var(--s-2) var(--s-4); text-align: left; }
.exp-pop button:hover { background: var(--blue-soft); color: var(--blue-bright); }

.picker { position: absolute; top: 100%; left: 0; right: 0; z-index: 50; padding: 0 clamp(16px, 3vw, 40px); }
.picker-scrim { display: none; }
.picker-panel {
  max-width: 1200px; margin: var(--s-2) auto 0;
  background: var(--surface-elev);
  border: 1px solid var(--border-strong); border-radius: var(--r-m); overflow: hidden;
  box-shadow: var(--shadow-elev);
  animation: pickerIn .18s ease both;
}
@keyframes pickerIn { from { opacity: 0; transform: translateY(-6px); } }
.picker-grid {
  padding: var(--s-4) var(--s-6) var(--s-1);
  display: grid; grid-template-columns: 2.2fr 1fr 1fr 1fr; gap: var(--s-1) var(--s-6);
  max-height: min(62dvh, 540px); overflow-y: auto;
}
.facet-wide { grid-row: 1 / 4; grid-column: 1; }
.facet-h {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-2);
  font-family: var(--mono); font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.1em; color: var(--text-3); padding: var(--s-2) var(--s-2) var(--s-1);
}
.facet-q {
  flex: 1 1 auto; min-width: 0; max-width: 190px; padding: var(--s-1) var(--s-2);
  border: 1px solid var(--border); border-radius: var(--r-s); background: var(--surface);
  outline: none; font-size: 12px; text-transform: none; letter-spacing: normal;
}
.facet-q:focus { border-color: var(--blue-border); }
.opts-2col { columns: 2; column-gap: var(--s-4); }
.opts-2col .opt { break-inside: avoid; }
.opt {
  display: flex; align-items: center; gap: var(--s-2); padding: var(--s-1) var(--s-2);
  border-radius: var(--r-s); font-size: 13px; color: var(--text-2); cursor: pointer;
}
.opt:hover { background: var(--surface-2); color: var(--text); }
.opt:focus-visible { outline: 2px solid var(--blue-border); outline-offset: 1px; }
.opt.zero { opacity: 0.38; }
.opt-tick {
  flex: 0 0 auto; width: 15px; height: 15px; border: 1px solid var(--border-strong);
  border-radius: var(--r-s); display: flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1;
}
.opt[data-state="inc"] { color: var(--text); }
.opt[data-state="inc"] .opt-tick { background: var(--blue); border-color: var(--blue); }
.opt[data-state="inc"] .opt-tick::before { content: "✓"; color: #fff; }
.opt[data-state="exc"] .opt-label { text-decoration: line-through; color: var(--exc); }
.opt[data-state="exc"] .opt-tick { border-color: var(--exc-border); }
.opt[data-state="exc"] .opt-tick::before { content: "⊘"; color: var(--exc); font-size: 11px; }
.opt-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.opt-n { font-family: var(--mono); font-size: 11px; color: var(--text-3); }
.opt-not {
  flex: 0 0 auto; width: 24px; height: 22px; border: none; border-radius: var(--r-s);
  background: transparent; color: var(--text-3); font-size: 12px; opacity: 0;
}
.opt:hover .opt-not, .opt:focus-within .opt-not { opacity: 1; }
.opt-not:hover { color: var(--exc); background: var(--exc-soft); }
@media (hover: none) { .opt-not { opacity: 1; } }
.picker-foot {
  padding: var(--s-3) var(--s-6) var(--s-4);
  display: flex; justify-content: space-between; align-items: center; gap: var(--s-3);
  border-top: 1px solid var(--border);
}
.ghost-btn {
  border: 1px solid var(--border); border-radius: var(--r-s); background: transparent;
  color: var(--text-2); font-size: 13px; padding: var(--s-2) var(--s-3);
}
.ghost-btn:hover { color: var(--text); border-color: var(--border-strong); }
.done-btn {
  border: none; border-radius: var(--r-s); background: var(--blue); color: #fff;
  font-size: 13px; font-weight: 500; padding: var(--s-2) var(--s-4);
}
.done-btn:hover { background: var(--blue-bright); }

@media (max-width: 720px) {
  .pill-rail { order: 10; flex-basis: 100%; min-height: 0; }
  .hit-count { font-size: 11px; }
  .picker { position: fixed; inset: 0; z-index: 90; padding: 0; }
  .picker-scrim { display: block; position: absolute; inset: 0; background: rgba(0,0,0,0.55); }
  .picker-panel {
    position: absolute; left: 0; right: 0; bottom: 0; max-height: 82dvh; max-width: none; margin: 0;
    display: flex; flex-direction: column;
    background: var(--surface-elev);
    border-radius: 16px 16px 0 0; border: none; border-top: 1px solid var(--border-strong);
    animation: sheetIn .22s ease both;
  }
  @keyframes sheetIn { from { transform: translateY(24px); opacity: 0.4; } }
  .picker-grid { grid-template-columns: 1fr; max-height: none; overflow-y: auto; flex: 1 1 auto; padding-bottom: var(--s-3); }
  .facet-wide { grid-row: auto; grid-column: auto; }
  .opts-2col { columns: 1; }
  .facet:not(.facet-wide) .opts { display: flex; flex-wrap: wrap; gap: var(--s-1); padding: 0 var(--s-2) var(--s-1); }
  .facet:not(.facet-wide) .opt {
    border: 1px solid var(--border); border-radius: var(--r-pill); padding: var(--s-1) var(--s-3); font-size: 12px;
  }
  .facet:not(.facet-wide) .opt .opt-tick { display: none; }
  .facet:not(.facet-wide) .opt[data-state="inc"] { border-color: var(--blue-border); background: var(--blue-soft); color: var(--blue-bright); }
  .facet:not(.facet-wide) .opt[data-state="exc"] { border-color: var(--exc-border); background: var(--exc-soft); }
  .picker-foot { padding-bottom: calc(14px + env(safe-area-inset-bottom)); border-top: 1px solid var(--border); }
}

.wm-link { color: inherit; text-decoration: none; }
.wm-link:hover .wm-a { color: var(--blue-bright); }

/* v2 tabs */
.tabs-in { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.tabs-in::-webkit-scrollbar { display: none; }
.tabs-in .tab { white-space: nowrap; }
/* THE PIN IS NOT HERE EITHER. This block is the strip's surface and its safe-area padding. That it
   is pinned — at top: 0, above the control band, and that the wordmark bar above it is not — is
   declared once, in THE PINNED CHROME near the top of this file, together with the height this
   strip is held to. */
.tabs {
  border-bottom: 1px solid var(--border); background: var(--bg-deep);
  /* The topmost pinned element, so it is what sits under the notch in landscape. */
  padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right);
}
.tabs-in { max-width: 1280px; margin: 0 auto; padding: 0 clamp(20px, 4vw, 48px); display: flex; gap: var(--s-1); }
.tab { font-family: var(--disp); font-size: 15px; font-weight: 500; color: var(--text-2);
  text-decoration: none; padding: var(--s-3) var(--s-4); border-bottom: 2px solid transparent; }
.tab:hover { color: var(--text); }
.tab.on { color: var(--blue-bright); border-bottom-color: var(--blue); }
/* constitution + flow browse */
.cx-wrap { max-width: 1280px; margin: 0 auto; padding: 10px clamp(20px, 4vw, 48px) 80px; }
.cx-note { font-family: var(--mono); font-size: 12px; color: var(--amber); border: 1px solid rgba(249,171,0,.35);
  background: rgba(249,171,0,.08); border-radius: var(--r-s); padding: var(--s-3) var(--s-4); margin: var(--s-4) 0; }
.cx-cat { margin-top: var(--s-8); }
.cx-cat h2 { font-family: var(--disp); font-weight: 500; font-size: 24px; color: var(--white); letter-spacing: -0.015em; }
.cx-grid { margin-top: var(--s-3); display: grid; gap: var(--s-3); grid-template-columns: repeat(auto-fill, minmax(min(340px,100%),1fr)); }
.cx-sec { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-m); padding: var(--s-4) var(--s-5); }
.cx-sec h3 { font-family: var(--disp); font-size: 15px; font-weight: 500; color: var(--white); }
.cx-sec p { color: var(--text-2); font-size: 13px; margin-top: var(--s-1); }
.cx-tags { margin-top: var(--s-3); display: flex; gap: var(--s-1); flex-wrap: wrap; }

/* constitution detail expansion */
.cx-sec { cursor: pointer; transition: border-color .15s; }
.cx-sec:hover, .cx-sec.open { border-color: var(--border-strong); }
.cx-sec:focus-visible { outline: 2px solid var(--blue-border); outline-offset: 1px; }
.cx-sec.open { grid-column: 1 / -1; }
.cx-detail { margin-top: var(--s-3); border-top: 1px solid var(--border); padding-top: var(--s-1); }
.cx-detail h4 { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: .1em; color: var(--text-3); margin: var(--s-3) 0 var(--s-1); }
.cx-detail ul { margin: 0; padding-left: var(--s-4); color: var(--text-2); font-size: 13px; }
.cx-detail li { margin: var(--s-1) 0; }
.cx-rid { font-family: var(--mono); font-size: 11px; color: var(--blue-bright); font-weight: 500; margin-right: var(--s-1); }
.cx-golds { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.cx-gold { font-family: var(--mono); font-size: 11px; color: var(--text-2); text-decoration: none;
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: var(--s-1) var(--s-3); }
.cx-gold:hover { color: var(--blue-bright); border-color: var(--blue-border); }
.cx-gold.consensus { color: var(--blue-bright); border-color: var(--blue-border); background: var(--blue-soft); }

/* constitution export + highlighting */
/* THE EXPORT CONTROL WAS STRANDED. Measured at 1440: the row spans 128→1312, the note stops at
   883 and Export sat at 895–978 — leaving 334px, a quarter of the row, empty to its right. The
   note is prose and must keep a reading measure, so the fix is not to stretch it; it is to send
   the control to the edge it belongs on. */
.cx-topline { display: flex; gap: var(--s-3); align-items: flex-start; margin: var(--s-4) 0; }
.cx-topline .export { margin-left: auto; }
.hl-num { font-family: var(--mono); font-size: 12px; color: var(--blue-bright); }
.hl-code { font-family: var(--mono); font-size: 12px; color: var(--green); background: var(--surface-2); border-radius: var(--r-s); padding: var(--s-05) var(--s-1); }
.hl-term { font-family: var(--mono); font-size: 11px; color: var(--amber); }
.cx-detail ul li::marker { color: var(--text-3); }
.cx-detail h4:nth-of-type(2) ~ ul li::marker { color: var(--amber); }
.cx-copy {
  margin-top: var(--s-4); font-family: var(--mono); font-size: 11px;
  border: 1px solid var(--border); border-radius: var(--r-s); background: var(--surface-2);
  color: var(--text-2); padding: var(--s-2) var(--s-3);
}
.cx-copy:hover { color: var(--blue-bright); border-color: var(--blue-border); }
@media (max-width: 720px) { .cx-topline { flex-direction: column; } }

/* problematic examples */
.cx-prob-note { font-size: 11px; color: var(--text-3); text-transform: none; letter-spacing: normal; margin-left: var(--s-1); }
.cx-prob { margin: var(--s-05) 0; padding-left: var(--s-4); color: var(--text-2); font-size: 13px; }
.cx-prob li { margin: var(--s-1) 0; }
.cx-prob b { color: var(--text); font-weight: 500; }
.cx-viol { font-family: var(--mono); font-size: 11px; color: var(--amber); }
.cx-cap { font-family: var(--mono); font-size: 11px; color: var(--text-3); white-space: nowrap; }

/* evidence gate — amber while a section is short of its capture quota, green once met */
.chip.ev {
  font-family: var(--mono);
  background: rgba(249, 171, 0, 0.10); border-color: rgba(249, 171, 0, 0.35); color: var(--amber);
}
.chip.ev.ok { background: rgba(52, 168, 83, 0.12); border-color: rgba(52, 168, 83, 0.4); color: var(--green); }
.cx-ev { color: var(--text-2); font-size: 13px; margin: var(--s-05) 0 0; }

/* ── Contrastive pairs ───────────────────────────────────── */
.pr-wrap { max-width: 1280px; margin: 0 auto; padding: 10px clamp(20px, 4vw, 48px) 80px; }
.pr-filter { display: flex; flex-wrap: wrap; gap: var(--s-1); margin: var(--s-3) 0 var(--s-4); }
.pr-chip {
  font-family: var(--mono); font-size: 11px; padding: var(--s-1) var(--s-2); border-radius: var(--r-pill);
  border: 1px solid var(--border); background: var(--surface); color: var(--text-2); cursor: pointer;
}
.pr-chip:hover { color: var(--text); }
.pr-chip.on { background: var(--blue-soft); border-color: var(--blue-border); color: var(--blue-bright); }
.pr-list { display: flex; flex-direction: column; gap: var(--s-5); }
.pr-pair { border: 1px solid var(--border); border-radius: var(--r-m); background: var(--surface); padding: var(--s-3) var(--s-3) var(--s-4); }
.pr-head { display: flex; flex-wrap: wrap; gap: var(--s-1); align-items: center; margin-bottom: var(--s-3); }
.pr-sides { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.pr-side { border: 1px solid var(--border); border-radius: var(--r-m); overflow: hidden; background: var(--bg); }
.pr-side.chosen { border-color: rgba(52, 168, 83, 0.4); }
.pr-side.rejected { border-color: rgba(249, 171, 0, 0.35); }
.pr-label {
  font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase;
  padding: var(--s-1) var(--s-3); border-bottom: 1px solid var(--border);
}
.pr-side.chosen .pr-label { color: var(--green); background: rgba(52, 168, 83, 0.08); }
.pr-side.rejected .pr-label { color: var(--amber); background: rgba(249, 171, 0, 0.07); }
.pr-shot { display: block; aspect-ratio: 16 / 10; overflow: hidden; background: #0b0b0c; }
.pr-shot img { width: 100%; height: 100%; object-fit: cover; object-position: top center; display: block; }
.pr-shot .noshot {
  display: grid; place-items: center; height: 100%;
  font-family: var(--mono); font-size: 11px; color: var(--text-3);
}
.pr-meta { padding: var(--s-2) var(--s-3) 0; font-size: 13px; display: flex; gap: var(--s-2); align-items: baseline; flex-wrap: wrap; }
.pr-side p {
  padding: var(--s-1) var(--s-3) var(--s-3); margin: 0; font-size: 13px; line-height: 1.55; color: var(--text-2);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
@media (max-width: 820px) {
  .pr-wrap { padding: 0 var(--s-3) var(--s-16); }
  .pr-sides { grid-template-columns: 1fr; }
  /* Section chips stay visually compact but carry a 44px touch box. */
  .pr-chip { position: relative; }
  .pr-chip::after {
    content: ''; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%); width: 100%; height: max(100%, 44px);
  }
}

/* Section filter: chip rail on desktop, select on phones — the rail is 26 chips, which
   is thirteen rows of scrolling before the first pair on a 393px screen. */
.pr-pick { display: none; }
.pr-pick select {
  width: 100%; padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-s);
  background: var(--surface); color: var(--text); font-family: var(--mono); font-size: 12px;
  -webkit-appearance: none; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--text-2) 50%), linear-gradient(135deg, var(--text-2) 50%, transparent 50%);
  background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}
@media (max-width: 820px) {
  .pr-filter { display: none; }
  .pr-pick { display: block; margin: var(--s-3) 0 var(--s-4); }
  .pr-pick span {
    display: block; font-family: var(--mono); font-size: 11px; text-transform: uppercase;
    letter-spacing: .08em; color: var(--text-3); margin-bottom: var(--s-1);
  }
  /* the caveat is essential on desktop and a wall of mono on a phone */
  .cx-topline .cx-note { font-size: 11px; line-height: 1.5; }
}


/* ── Critic pass ─────────────────────────────────────────── */

/* #23 object-fit: cover sliced headlines through the letterforms, and the fix was a mask that
   faded the bottom 14% of every capture to transparent:

     .card-media img { mask-image: linear-gradient(to bottom, #000 86%, transparent 100%); }

   LA: he dislikes it, and the renders agree with him. It was the wrong instrument for a real
   problem. A fade does not END an image, it dissolves one — and what it dissolves INTO decides
   how bad it looks, which means it looked different on all 739 cards and good on none:

     · a pale capture on the dark theme faded to #1a1b1f and turned to grey mud across the bottom
       eighth of the picture — measured on the NYT Magazine, Vercel and Tailwind cards
     · a pale capture on the LIGHT theme faded into a white card and simply stopped existing, with
       no edge at all: the image had no bottom
     · a near-black capture did the same in reverse
     · and on the Templates tab the blank variants, which are pale grey by design, lost their
       lower eighth entirely — the one place a reader is looking for the geometry

   It also never solved #23. A headline sliced by `object-fit: cover` is still sliced; the fade
   just makes the slice foggy. The real answer is above: the capture is a PRINT ON A MOUNT, with
   4px of card showing on every side and an inset hairline over the picture. A crop reads as
   intentional because something frames it — not because it trails off.

   The mask is deleted rather than overridden. `.pr-shot` does not exist anywhere in v5 (it was the
   Pairs tab, which stayed in v4), so half this selector had been dead since the version split. */

/* #23, ACTUALLY: the crop is announced by the frame around it, not by a fade inside it. */

/* #4 154 cards with no headings is not reviewable */
.pr-sec-head { margin: var(--s-8) 0 var(--s-2); display: flex; align-items: baseline; gap: var(--s-2); }
.pr-sec-head h2 { font-family: var(--disp); font-weight: 500; font-size: clamp(20px, 2.4vw, 27px); letter-spacing: -0.015em; color: var(--white); }
.pr-sec-head .n { font-family: var(--mono); color: var(--text-3); font-size: 12px; }

/* #13 same role, two scales, only one of them fluid */
.genre-head h2, .cx-cat h2 { font-size: clamp(20px, 2.4vw, 27px); }

/* #15 half the captures are links and nothing said which */
a.pr-shot { cursor: pointer; }

@media (max-width: 820px) {
  /* #6 Flow reuses .row but has no Cards view to fall back to, so hiding .row-why
     shipped a phone experience with none of the tab's content in it. */
  .cx-wrap .row { grid-template-columns: 1fr auto; }
  .cx-wrap .row-why { display: block; grid-column: 1 / -1; white-space: normal; font-size: 13px; margin-top: var(--s-05); }
  /* #22 Export was orphaned on its own row above the section picker */
  .pr-pick { display: flex; align-items: flex-end; gap: var(--s-2); }
  .pr-pick select { flex: 1 1 auto; }
}

/* #20 the primary navigation was the one control under the touch floor */
@media (max-width: 720px) {
  .tab { position: relative; }
  .tab::after { content: ''; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 100%; height: max(100%, 44px); }
}

/* #24 hero copy ended on two-word orphan lines */
.hero-sub { max-width: 62ch; text-wrap: pretty; }

/* Rubric chips are neutral: amber is reserved for the rejected/shortfall semantic, and an
   amber chip beside a rejected panel read as belonging to it. */
.chip.rub { font-family: var(--mono); color: var(--text-2); border-color: var(--border); background: var(--surface-2); }

/* export chooser — three datasets, one clear contract */
.exp-pop { min-width: 300px; }
.exp-group-label { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; opacity: .55; padding: var(--s-2) var(--s-2) var(--s-05); }
.exp-link { display: block; padding: var(--s-2) var(--s-2); border-radius: var(--r-s); text-decoration: none; color: inherit; font-size: 13px; }
.exp-link:hover { background: rgba(127,127,127,.12); }
.exp-hint { display: block; font-size: 11px; opacity: .55; }
.exp-warn { font-size: 11px; opacity: .65; padding: var(--s-2) var(--s-2) var(--s-1); border-top: 1px solid rgba(127,127,127,.2); margin-top: var(--s-1); }

@media (max-width: 600px) {
  .exp-pop { position: fixed; left: 12px; right: 12px; top: auto; max-width: none; }
}

/* iOS Safari auto-zooms any focused control under 16px — the passcode field zoomed
   before a reviewer saw anything. Chromium emulation cannot reproduce this. */
@media (max-width: 820px) {
  #gate-input, .search input, #facet-q, select, .pr-filters select { font-size: 15px; }
}

/* corpus taxonomy panel — lives on the Design tab, toggled from the bar */
.taxo { border:1px solid var(--border); background:var(--surface); border-radius: var(--r-l); padding:var(--s-4) var(--s-5); margin:0 auto var(--s-5); max-width:1280px; }
.taxo h2 { margin:0 0 var(--s-1); font-size:17px; }
.taxo .tx-line { font-size:13px; color:var(--text-2); line-height:1.6; margin:0 0 var(--s-3); }
.taxo h3 { margin:var(--s-4) 0 var(--s-2); font-size:12px; letter-spacing:.08em; text-transform:uppercase; color:var(--text-2); font-family:"Google Sans Code",monospace; }
.tx-splits { display:grid; gap:var(--s-2); }
.tx-split { display:flex; flex-wrap:wrap; gap:var(--s-1) var(--s-2); align-items:baseline; font-size: 12px; }
.tx-old { font-family:"Google Sans Code",monospace; color:var(--amber,var(--amber)); white-space:nowrap; }
.tx-arrow { color:var(--text-2); }
.tx-arch { display:grid; grid-template-columns:repeat(auto-fill,minmax(min(230px,100%),1fr)); gap:var(--s-2); }
.tx-cell { border:1px solid var(--border); border-radius: var(--r-m); padding:var(--s-2) var(--s-3); background:var(--bg); display:flex; flex-direction:column; }
.tx-cell > div { display:flex; flex-wrap:wrap; gap:var(--s-1); }
.tx-cell b { font-size: 12px; display:block; margin-bottom:var(--s-1); font-family:"Google Sans Code",monospace; }
.tx-cell b i { font-style:normal; opacity:.6; }
.taxo .chip { display:inline-block; white-space:nowrap; text-transform:none; letter-spacing:0; font-size:11px; font-family:Inter,sans-serif; max-width:100%; overflow:hidden; text-overflow:ellipsis; vertical-align:top; margin:0; }

/* Four tabs exceed a 393px viewport by 14px — the rail scrolls rather than overflows. */
@media (max-width: 480px) {
  .tabs-in { overflow-x: auto; flex-wrap: nowrap; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .tabs-in::-webkit-scrollbar { display: none; }
  .tabs-in .tab { white-space: nowrap; }
}

/* ── the v2→v3 changelog: a diff, not a splash. Shown once, reopenable, no marketing. ── */
.chlog { position: fixed; inset: 0; z-index: 90; display: grid; place-items: center; padding: var(--s-5); }
.chlog-scrim { position: absolute; inset: 0; background: rgba(8,10,14,.72); }
@media (min-width: 768px) { .chlog-scrim { backdrop-filter: blur(14px) saturate(1.1); -webkit-backdrop-filter: blur(14px) saturate(1.1); } }
.chlog-card { position: relative; width: min(660px, 100%); max-height: min(82dvh, 720px); overflow: auto; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-l); box-shadow: 0 24px 80px rgba(0,0,0,.5); }
.chlog-head { display: flex; align-items: center; justify-content: space-between; padding: var(--s-4) var(--s-5) 0; }
.chlog-tag { font-family: "Google Sans Code", monospace; font-size: 13px; letter-spacing: .06em; color: var(--blue,var(--blue)); }
.chlog-x { background: none; border: none; color: var(--text-2); font-size: 22px; cursor: pointer; line-height: 1; width: 44px; height: 44px; display: grid; place-items: center; margin: -8px -12px 0 0; }
.chlog-body { padding: var(--s-2) var(--s-5) var(--s-1); }
.chlog-body h2 { margin: var(--s-1) 0 var(--s-3); font-size: 20px; }
.chlog-line { display: grid; grid-template-columns: 18px 1fr; gap: var(--s-2); padding: var(--s-2) 0; font-size: 13px; line-height: 1.55; color: var(--text-2); border-top: 1px solid color-mix(in srgb, var(--border) 55%, transparent); }
.chlog-line:first-of-type { border-top: none; }
.chlog-line b { color: var(--text); font-weight: 600; }
.chlog-line .op { font-family: "Google Sans Code", monospace; font-size: 13px; text-align: center; }
.chlog-line.add .op { color: var(--green,var(--green)); }
.chlog-line.mod .op { color: var(--amber,var(--amber)); }
.chlog-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); padding: var(--s-3) var(--s-5) var(--s-4); font-size: 12px; color: var(--text-2); border-top: 1px solid var(--border); }
.chlog-go { background: var(--blue,var(--blue)); color: #fff; border: none; border-radius: var(--r-pill); padding: var(--s-2) var(--s-4); font: inherit; font-size: 13px; cursor: pointer; }
.wm-new { font-family: "Google Sans Code", monospace; font-size: 11px; color: var(--text-2); background: none; border: 1px solid var(--border); border-radius: var(--r-pill); padding: var(--s-05) var(--s-2); cursor: pointer; margin-left: var(--s-2); position: relative; }
.wm-new::after { content: ''; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); width: 110px; height: 44px; }

/* The changelog trigger. It used to auto-open as a modal over the gallery, so the first thing a
   visitor saw on a corpus about visual design was a dialog explaining it. Now it sits in the
   header, with a dot when unread. */
.chlog-open { background: none; border: 1px solid var(--border); color: var(--text-2);
  font: inherit; font-size: 13px; padding: var(--s-1) var(--s-3); border-radius: var(--r-pill); cursor: pointer;
  position: relative; margin-left: var(--s-2); }
.chlog-open:hover { color: var(--text); border-color: var(--border-strong); }
.chlog-open.unread::after { content: ""; position: absolute; top: 3px; right: 3px; width: 6px;
  height: 6px; border-radius: 50%; background: var(--blue, var(--blue)); }

/* PAGE TITLES. Every tab except the gallery used `<h1 class="page-h">` with no rule behind it,
   so the browser default applied and a page's own title rendered smaller and heavier than the
   sub-headings under it. A critic reading the tabs as a set flagged exactly that. The scale is
   fluid so it holds from 390 to 1440 without a breakpoint. */
.page-h { font-family: var(--disp); font-weight: 500; letter-spacing: -.028em;
  font-size: clamp(30px, 4.2vw, 46px); line-height: 1.04; color: var(--white, #fff);
  margin: var(--s-1) 0 var(--s-2); text-wrap: balance; }
/* The line under a page title, where one follows. Sets the measure so a lede does not run the
   full width of a 1440 viewport, which is what made these tabs read as documents. */
.page-h + p, .page-lede { color: var(--text-2); font-size: 15px; line-height: 1.65;
  max-width: 66ch; margin-bottom: var(--s-6); text-wrap: pretty; }

/* The register strip in a component modal: the same section under all four surfaces. Two up on a
   phone, four across on desktop — at four abreast on a narrow screen each frame is too small to
   read, which would defeat the point of showing them. */
.regs { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-2); margin: var(--s-2) 0 var(--s-4); }
@media (min-width: 900px) { .regs { grid-template-columns: repeat(4, 1fr); } }
.reg { margin: 0; }
.reg img { width: 100%; display: block; border: 1px solid var(--border); border-radius: var(--r-s); background: var(--surface-2); }
.reg figcaption { margin-top: var(--s-1); font: 400 11px/1.3 var(--mono); color: var(--text-3); letter-spacing: .03em; }
.reg-lede { color: var(--text-2); font-size: 13px; line-height: 1.6; max-width: 70ch; margin: var(--s-05) 0 var(--s-3); }

/* Sub-tabs inside a tab. Deliberately quieter than the main tab strip — they switch a view
   within one surface rather than navigating, and making them look like the top-level nav would
   suggest they are pages. */
.subtabs { display: flex; gap: var(--s-1); margin: var(--s-1) 0 var(--s-2); flex-wrap: wrap; }
.subtab { background: none; border: 1px solid var(--border); color: var(--text-2); font: inherit;
  font-size: 13px; padding: var(--s-2) var(--s-3); border-radius: var(--r-pill); cursor: pointer; }
.subtab:hover { color: var(--text); border-color: var(--border-strong); }
.subtab.on { background: var(--blue-soft, rgba(46,150,255,.14)); color: var(--blue-bright, var(--blue-bright));
  border-color: var(--blue-border, rgba(46,150,255,.42)); }
.subtab-note { color: var(--text-2); font-size: 13px; line-height: 1.6; max-width: 70ch; margin: 0 0 var(--s-4); }

/* The constitution's topline note is PROSE, and it was set in 12px monospace running the full
   1280px measure — which is why the tab read as a terminal dump rather than a document. Mono is
   right for identifiers, rubric codes and numbers, and those keep it via <em>/<code> inside.
   The paragraph itself gets a reading face, a real measure and honest leading. */
.cx-topline .cx-note, .cx-note { font-family: var(--sans); font-size: 13px; line-height: 1.68;
  max-width: 92ch; text-wrap: pretty; }
.cx-note strong { font-family: var(--sans); }
.cx-note em { font-family: var(--mono); font-style: normal; font-size: .92em; letter-spacing: .01em; }
@media (max-width: 760px) { .cx-topline .cx-note, .cx-note { font-size: 13px; line-height: 1.6; } }

/* The phone tab strip scrolls (755px of tabs in a 390px viewport) and reaches Resources, but it
   gave no sign that it scrolled — the last visible tab simply ended at the edge, which reads as
   the end of the list rather than as more to come. A mask fades the trailing edge so the strip
   announces itself as scrollable, and it is removed once the user has scrolled to the end. */
@media (max-width: 760px) {
  /* THE PIN USED TO BE RE-DECLARED HERE, and this was the fourth of the four rules that collided.
     It said "sticky on a phone too", which was already true unconditionally from the rule above it
     — a restatement, not an override, and restating a value is how three of them drifted to the
     same top: 0. `.tabs` is pinned at every width by THE PINNED CHROME near the top of this file,
     and sticky establishes the containing block the mask below needs, so nothing is lost. */
  .tabs-in { mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 34px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 34px), transparent 100%); }
  .tabs-in.at-end { mask-image: none; -webkit-mask-image: none; }
}


/* ── LINKS ─────────────────────────────────────────────────────────────────────────────────────
   This stylesheet defined colours for the wordmark and the tabs and never for an ordinary link, so
   every link in prose or in a table fell back to the user agent: #0000EE, and #551A8B once visited.
   On a dark ground that reads as broken markup, and it is the single biggest reason the Resources
   tab looked unfinished. Underlines are kept — the links sit inside tables and dense prose where
   colour alone is not a reliable affordance — but thinned so they do not fight the type.
   .tp-hv is in the :not() list because it is a CONTROL, not prose: the provenance (line version) pill on the
   Templates rows draws its own border and quiet ink, and this rule's seven :not() classes outrank
   any single-class pill rule — measured: the pill shipped blue and underlined inside its border
   until the exemption was added here, the same way .chip and .t-go already are. */
main a:not(.tab):not(.wm-link):not(.tp-hv):not(.chip):not(.p20-card):not(.cx-gold):not(.t-go):not(.t-lnk),
.wrap a:not(.tab):not(.wm-link):not(.tp-hv):not(.chip):not(.p20-card):not(.cx-gold):not(.t-go):not(.t-lnk),
.res a:not(.tab):not(.chip),
.res-table a:not(.tab):not(.chip),
.tab-intro a, .skel-note a, .modal-body a:not(.chip) {
  color: var(--blue-bright);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--blue-bright) 42%, transparent);
  text-underline-offset: 2px;
}
main a:not(.tab):not(.wm-link):not(.tp-hv):not(.chip):not(.t-go):not(.t-lnk):hover,
.wrap a:not(.tab):not(.wm-link):not(.tp-hv):not(.chip):not(.t-go):not(.t-lnk):hover,
.res a:not(.tab):not(.chip):hover,
.res-table a:not(.tab):not(.chip):hover,
.tab-intro a:hover, .modal-body a:not(.chip):hover {
  text-decoration-color: var(--blue-bright);
}
main a:visited:not(.tab):not(.wm-link):not(.tp-hv):not(.chip):not(.t-go):not(.t-lnk),
.wrap a:visited:not(.tab):not(.wm-link):not(.tp-hv):not(.chip):not(.t-go):not(.t-lnk),
.res a:visited:not(.tab):not(.chip),
.res-table a:visited:not(.tab):not(.chip) { color: var(--blue-bright); }


/* ── TAB INTRO ─────────────────────────────────────────────────────────────────────────────────
   Every tab's title and standfirst, in ONE place.
   These rules lived inside each page's own <style> block, duplicated across eight pages, and the
   ninth tab (Pretty 20) simply did not have them — so its h1 fell back to the browser default and
   sat flush against the window edge while the tab strip and the cards were inset. LA: "all title
   and subtitle must look the same". They can only be guaranteed to match if they are defined once.

   One container width for the intro, the nav and the content, so the top of a page has ONE left
   edge rather than three: 1280px with the same fluid padding the tab strip uses. */
.tab-intro { max-width: 1280px; margin: 0 auto; padding: 1.9rem clamp(20px, 4vw, 48px) 1.5rem; }
.tab-intro h1 { font-family: var(--disp); font-size: clamp(30px, 4.6vw, 48px); font-weight: 500;
                letter-spacing: -.025em; line-height: 1.08; color: var(--white); margin: 0 0 var(--s-2); }
.tab-intro .what { margin: 0; font-size: .98rem; line-height: 1.5; }
.tab-intro .why { margin: var(--s-1) 0 0; font-size: .9rem; line-height: 1.5; opacity: .6; }
@media (max-width: 760px) {
  .tab-intro { padding: var(--s-6) var(--s-5) var(--s-4); }
  .tab-intro h1 { font-size: clamp(26px, 7vw, 34px); }
}


/* ── SECTION TITLES ────────────────────────────────────────────────────────────────────────────
   One treatment, everywhere. Measured before this rule: .genre-head h2 and .cx-cat h2 rendered
   27px Google Sans Flex 500 at x=128, while .taxo h2, .findings h2, .res h2 and .howto h2 rendered
   17px Inter 700 at four different left edges. Two of eight tabs used the display face for the
   role that most defines whether a page reads as designed. */
.genre-head h2, .cx-cat h2, .taxo h2, .findings h2, .res h2, .howto h2, .res-table h2 {
  font-family: var(--disp); font-weight: 500;
  font-size: clamp(20px, 2.4vw, 27px); letter-spacing: -0.015em;
  color: var(--white); line-height: 1.15;
}

/* ── SEMANTIC COLOUR — one colour, one meaning, everywhere ─────────────────────────────────────
   LA: "une couleur doit toujours dire la même chose."
   Measured before this block, three colours carried more than one meaning:

     AMBER  meant "warning" (.cx-viol, a rubric violation) AND "excellence" (.anchor, the taste
            anchors — Teenage Engineering, Ressence, Apple — and .cx-gold, the golden examples).
            A star you chose and a rule you broke rendered identically. That is the worst of them.
     GREEN  meant "success", "the chosen side of a pair", and "golden example".
     PURPLE meant two unrelated taxonomy axes.

   The fix is not new hexes. It is deciding what each colour SAYS, then naming that, so a call site
   asks for the meaning rather than the colour. The palette below is unchanged in hue — it is the
   same blue, green, amber and red already in the corpus — only its assignment is now single-valued.

     --sig-good      this is exceptional, chosen, passing        → green
     --sig-warn      this is degraded, partial, needs attention  → amber
     --sig-bad       this failed, was rejected, was removed      → red
     --sig-info      this is interactive: links, active state    → blue
     --sig-note      this is a taxonomy or a neutral label       → purple

   Excellence therefore reads GREEN, not amber. A violation reads RED, not amber. Amber is reserved
   for the one honest middle state: partial. */
:root {
  --sig-good:        var(--green);
  --sig-good-soft:   var(--green-soft);
  --sig-warn:        var(--amber);
  --sig-warn-soft:   var(--amber-soft);
  --sig-bad:         var(--red);
  --sig-bad-soft:    var(--red-soft);
  --sig-info:        var(--blue-bright);
  --sig-info-soft:   var(--blue-soft);
  --sig-note:        var(--purple);
}

/* EXCELLENCE is green. These were amber, alongside warnings.
   `.cx-gold` is an <a>, so the generic link rule outranked the first version of this and golden
   examples rendered blue — the interactive colour — instead of the excellence colour. Meaning has
   to win over role, so it is stated at higher specificity. */
.row-name .anchor, .card-top .anchor,
.cx-golds, .plate-card.golden .plate-tag,
main a.cx-gold:not(.tab), .wrap a.cx-gold:not(.tab), a.cx-gold { color: var(--sig-good); }
.cx-gold { border-color: color-mix(in srgb, var(--sig-good) 40%, transparent); }

/* A VIOLATION is red. It was amber, alongside the taste anchors. */
.cx-viol { color: var(--sig-bad); border-color: color-mix(in srgb, var(--sig-bad) 40%, transparent); }

/* PARTIAL — the one honest middle state — keeps amber, and is now the only thing that uses it. */
.st-part, .chip.ev { color: var(--sig-warn); }

/* A FAILURE has to look like one. `.st-no` rendered in --text-3 grey, so "no" was indistinguishable
   from ordinary muted text and the three-state badge only had two visible states. */
.st-no { color: var(--sig-bad); background: var(--sig-bad-soft); }


/* ── TOUCH + FOCUS FLOOR ───────────────────────────────────────────────────────────────────────
   .modal-x is 36x36 and was the only way out of three modals on a phone. .pill-x measured 22x22
   and .opt-not 24x22, both under the 24px minimum and both sitting flush against a neighbour, so
   the spaced-target exception does not apply. */
.modal-x { min-width: 44px; min-height: 44px; }
.pill-x { min-width: 24px; min-height: 24px; }
.opt-not { min-width: 24px; min-height: 24px; }

/* One focus ring for the whole site. There were exactly two :focus-visible rules in 842 lines and
   both used --blue-border, which composites to 2.04:1 against the ground — under the 3:1 an
   indicator needs. Everything else fell back to the user-agent ring, which is not designed for
   this palette. --blue-bright is 8.63:1. */
:where(a, button, [tabindex], input, select, textarea, summary, .card, .cx-sec, .opt):focus-visible {
  outline: 2px solid var(--blue-bright);
  outline-offset: 2px;
  border-radius: inherit;
}
.opt:focus-visible, .cx-sec:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 2px; }
/* The affordance telling you a card is actionable was withheld from keyboard users. */
.card:focus-visible .card-visit, .card:focus-within .card-visit { opacity: 1; }

/* Focus must not land under the sticky chrome. Measured: 173px of it on the gallery, and
   scroll-padding-top computed to `auto` — there was none anywhere in the stylesheet. */
html { scroll-padding-top: 180px; }
@media (max-width: 900px) { html { scroll-padding-top: 140px; } }

/* Skip link — with a sticky 8-item strip plus a filter bar, a keyboard user passed ~15 controls
   before reaching content on every single navigation. */
.skip { position: absolute; left: -9999px; }
/* POSITION: FIXED, NOT ABSOLUTE. `.skip` is `position: absolute`, so `top: 12px` on focus meant
   12px from the top of the DOCUMENT, not of the viewport. On the corpus tab — 113,666px tall at
   1440 and 280,611px at 375 — a reader who scrolls and then presses Tab focuses a control sitting
   thousands of pixels above the fold. Measured at scrollY 4000: the focused skip link reported
   y = -3988. Focusable, first in the tab order, and off screen: WCAG 2.2 SC 2.4.11 Focus Not
   Obscured. templates.html already had `position: fixed` in its inline block, which is why the
   defect never appeared there and was never noticed here. */
.skip:focus { position: fixed; left: 12px; top: 12px; z-index: 200; padding: var(--s-2) var(--s-4);
  background: var(--surface-elev); border: 1px solid var(--blue-bright);
  border-radius: var(--r-s); color: var(--text); }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

.gate-err { color: var(--red); min-height: 1.2em; font-size: 13px; margin: var(--s-1) 0 0; }

/* The third intro line, folded in from the .hero block that used to sit below it. Slightly wider
   measure than .why because it carries the substance, not the standfirst. */
.tab-intro .why.lede { margin-top: var(--s-2); max-width: 68ch; opacity: .78; }


/* ── FOLD ──────────────────────────────────────────────────────────────────────────────────────
   ONE DISCLOSURE, EVERYWHERE. LA: "make sure that explanations are compact."

   The substance is worth keeping — the team lead and the eval engineers need to know why a tab exists and
   how it was made — but it is not worth being the fold. Measured before this rule: Resources
   carried 7,348 characters of prose in 23 paragraphs with two blocks over 700 characters each,
   and three tabs opened on a four-line standfirst before the first artifact.

   So every tab now shows ONE line, and the argument lives one click away. Nothing is deleted.

   This pattern already existed as `.ask-fold`, defined inside skeletons.html's own <style> block —
   the same duplication that had the tab-intro rules living in eight places and missing from the
   ninth. Lifting it here also fixes a corrupted declaration in that copy: the summary read
   `Kpx; letter-spacing: .04em`, a font-size mangled by the type-scale codemod, so the one element
   whose whole job is to be a legible affordance had no size of its own. */
.fold, .ask-fold {
  margin: 0 0 var(--s-6); border: 1px solid var(--border); border-radius: var(--r-m);
  background: var(--surface);
}
.tab-intro .fold { margin: var(--s-3) 0 0; max-width: 68ch; }
.fold > summary, .ask-fold > summary {
  cursor: pointer; padding: var(--s-3) var(--s-4);
  font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .04em;
  color: var(--text-2); list-style: none;
  /* A summary is the control; it must clear the touch floor the rest of the site clears. */
  min-height: 44px; display: flex; align-items: center; gap: .5ch;
}
.fold > summary::-webkit-details-marker,
.ask-fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before, .ask-fold > summary::before { content: "\25B8"; color: var(--text-3); }
.fold[open] > summary::before, .ask-fold[open] > summary::before { content: "\25BE"; }
.fold > summary:hover, .ask-fold > summary:hover { color: var(--text); }
.fold[open] > summary, .ask-fold[open] > summary { border-bottom: 1px solid var(--border); }
.fold-body { padding: var(--s-3) var(--s-4) var(--s-1); }
.fold-body > p { margin: 0 0 var(--s-3); font-size: var(--t-body, .93rem); line-height: 1.55;
  color: var(--text-2); max-width: 68ch; }
.fold-body > p:last-child { margin-bottom: var(--s-2); }
.ask-fold .ask { padding: var(--s-4) var(--s-4) var(--s-1); }

/* The constitutions topline carried its colours in a style="" attribute — the one place on the
   site still painting outside the token layer. Same values, named. */
.cx-topnote { color: var(--text-2); border-color: var(--border); background: var(--surface);
  margin: 0; flex: 1 1 auto; }

/* The two gallery-shaped tabs (the gallery itself and Pretty 20) fold inside `.hero` rather than
   `.tab-intro`; give the disclosure the same measure and offset there. */
.hero .fold { margin: var(--s-3) 0 0; max-width: 68ch; }


/* ── DOWNLOAD BAR ──────────────────────────────────────────────────────────────────────────────
   Injected by downloads.js on every tab. Styled as a quiet utility row, not a call to action:
   the team is here to look at the corpus, and the files are a convenience beside it. */
/* Same reservation, same reason: downloads.js inserts this row about 1.2s in, ABOVE all the tab's
   content, so every tab shifted by its full height once the manifest resolved. Measured 63-65px.
   The min-height alone would not have fixed it — the element does not exist until the script runs,
   so there is nothing for a height to apply to. Each tab's markup now carries an empty .dl-bar,
   which downloads.js finds and fills rather than creates, and this reserves its height meanwhile. */
/* ── THE CONTROL BAR, ONE COMPONENT FOR ALL THREE TABS ────────────────────────────────────────
   LA: "this control bar must be beautiful consistent. Look solid across 3 tabs. this looks so
   Amateur... How can we pretend to work on pretty websites when we do this?"
   Fair. It was not one component. `.dl-bar` was a page-width row that centred itself with
   `margin: 0 auto` — correct when it WAS the row, wrong the moment a tab nested it inside its own
   toolbar, where the auto margins floated a 444px pill into the middle of a 1280px strip. Corpus
   nested it one way, Templates another, Vocabulary left it standing alone with the axis links
   orphaned on a second line below. Three tabs, three shapes, one class.
   `.ctl` is the row. It owns the page width, the gutters, the height and the rule beneath it.
   `.ctl-l` and `.ctl-r` are the two groups inside it.

   ONE ROW, EVERY CONTROL, PACKED LEFT. LA: "the layout is clearly a problem… I want this
   button/label to be in the same container as [the count/dice/shuffle/List/Cards group]. all
   control in line, on the left ok?"
   It was two groups pushed to opposite ends by `justify-content: space-between` plus
   `margin-left: auto`, with 600px of nothing between them — which is what made a single component
   read as two unrelated bars. They are one group now: no auto margins, no space-between, and the
   order set in CSS rather than in markup because the markup lives in three different places
   (index.html, vocabulary.html, and build-templates-tab.mjs, which belongs to someone else this
   week). Renaming the classes would have been honest and would also have meant editing all three;
   `order` gets the same row on every tab from one line.
   Reading order left to right: what the tab LETS YOU DO (count, dice, shuffle, view — `.ctl-r`),
   then what it GIVES YOU (the dataset pill — `.ctl-l`), then what is APPLIED (the facet rail).
   `.dl-bar` becomes a plain flex item and stops trying to be a layout. */
.ctl { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-start;
  gap: var(--s-2) var(--s-3); max-width: 1280px; margin: 0 auto; min-height: 64px;
  padding: var(--s-2) clamp(20px, 4vw, 48px); }
.ctl-l, .ctl-r { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); min-width: 0; }
.ctl-r { order: 1; }
.ctl-l { order: 2; }
.ctl > .pill-rail { order: 3; }
/* AN EMPTY GROUP IS STILL A FLEX ITEM, AND A FLEX ITEM STILL EATS A GAP.
   Templates has nothing to put in `.ctl-r` — its `#tp-controls` is an empty div — so a 0×0 box sat
   at the head of the row and the 12px column gap after it pushed the dataset pill to x=420 while
   the same pill started at x=408 on the other two tabs. Twelve pixels, invisible on any one tab and
   exactly the kind of drift that makes three bars read as three bars. Remove the box, not the gap.
   The `:has` variant covers the other case: `.ctl-l` always contains a `.dl-bar`, so it is never
   `:empty` itself, but that bar is empty until downloads.js fills it — and stays empty on any tab
   with no dataset, which is a supported outcome (the manifest fetch fails soft, by design). */
.ctl-l:empty, .ctl-r:empty, .ctl .dl-bar:empty { display: none; }
.ctl-l:has(> .dl-bar:empty:only-child) { display: none; }
/* AND THE RAIL IS THE THIRD ONE, which this rule missed because on desktop it costs nothing.
   Below 720 the rail is `flex-basis: 100%`, so with no facet applied — the state every reader
   arrives in — it takes a zero-height wrapped row AND the 8px row-gap in front of it. That is 17px
   of dead space under the controls against 8px above, and it is half of why the phone bar looked
   bottom-heavy even after the margin above it was removed. Exactly the defect the two rules above
   already describe; the rail was simply not in the list. */
.ctl > .pill-rail:empty { display: none; }

/* THE HEIGHT AND THE COUNT'S BADGE USED TO BE DECLARED HERE. Both moved into THE CONTROL BAR
   GRAMMAR at the end of this file, and these copies are deleted rather than left to be overridden.
   Two rules for one control is how this stylesheet drifts: a red-team patch aimed at the count's
   radius landed on THIS block, changed nothing, and reported the gate as broken when the gate was
   right — the dead rule was the only thing that moved. The pill radius it set is also no longer the
   design: LA's grammar is one radius for every control in the row, not a shape per kind. */

.dl-bar { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; min-height: 0;
  max-width: none; margin: 0; padding: 0; }

/* ONE PILL PER DOWNLOAD, HOLDING ITS OWN TWO ACTIONS. LA: "what is in it is not clear — should we
   not have those two with pills within one, to see what is in there (the modal) and this other as
   actual download button?" The previous row was two downloads plus a third floating control, and
   "what is in it" could not say what IT was. Each pill now answers for itself: see inside, then
   take it. */
.dl {
  display: inline-flex; align-items: stretch;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface); overflow: hidden;
}
.dl:hover { border-color: var(--text-3); }
.dl-peek, .dl-get {
  display: inline-flex; align-items: center; gap: .6ch;
  min-height: 44px; padding: 0 var(--s-4);
  background: none; border: 0; color: var(--text-2);
  font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .04em;
  text-decoration: none; cursor: pointer; white-space: nowrap;
}
/* The divider is the whole point of a split control — without it the two halves read as one
   button and a reader cannot tell there are two things to press. */
.dl-get { border-left: 1px solid var(--border); }
.dl-peek:hover, .dl-get:hover { background: var(--surface-elev); color: var(--text); }
.dl-peek { color: var(--text-3); }
.dl-peek:hover { color: var(--text); }
/* The download half is the primary action of the pair, so it carries the label and the arrow. */
.dl-k { color: var(--text-3); text-transform: uppercase; }
.dl-k::after { content: "\00B7"; margin-left: .6ch; }
.dl-file { color: var(--text); }
/* The size, in its own span so a narrow viewport can drop it and keep the filename. The separator
   belongs to the size and not to the name: printed by the name, it would leave a trailing "·"
   pointing at nothing every time the size is hidden. */
.dl-size { color: var(--text); }
.dl-size::before { content: "\00B7"; margin: 0 .6ch 0 .5ch; color: var(--text-3); }
.dl-arrow { color: var(--blue-bright); font-size: 13px; }
.dl-get:hover .dl-arrow { transform: translateY(1px); }
.dl-arrow { transition: transform .15s; }
/* On a phone the two halves plus a filename overflow a 375px viewport, so each pill takes the
   full width and the split runs across it rather than off the edge. */
@media (max-width: 560px) {
  /* THREE FULL-WIDTH ROWS COST A QUARTER OF EVERY PHONE SCREEN.
     Stacking put the two download controls and the methodology badge on their own lines: 171px of
     identical chrome at the top of all nine tabs, 26% of a 660px viewport, before a reader reached
     anything the tab is actually about. Measured across the site, not one tab showed a single pixel
     of its own subject in the first phone screen.
     They wrap instead now. The filename and size drop out below 560px — a phone is not where you
     choose between a 1.9 MB zip and a 558 KB json, and both are stated in full inside the preview
     the same control opens. What survives is what a phone reader needs: the label, the arrow, the
     target size. Three rows become one. */
  .dl-bar { flex-direction: row; flex-wrap: wrap; align-items: center; min-height: 0; }
  .dl { width: auto; flex: 0 1 auto; }
  .dl-get { flex: 0 1 auto; justify-content: flex-end; }
  .dl-peek { flex: 0 0 auto; }
  /* THE SIZE GOES, THE NAME STAYS. This hid `.dl-file`, which held the whole string
     "teardowns.zip · 1.9 MB" — fine while a "THIS TAB" label sat beside it, and a button reading
     nothing but ↓ once that label was removed at LA's request. A control with no text is not a
     compact control, it is an unlabelled one. */
  .dl-get .dl-size { display: none; }
  .dl-peek, .dl-get { padding-left: var(--s-3); padding-right: var(--s-3); }
  /* THE DOWNLOAD BUTTON WAS CUT OFF AT 375. `.dl` clips its overflow, and `.dl-get` had no
     min-width, so a flex item refuses to shrink below its content and the pill overshot its
     container by 55px on Resources — the filename ran off mid-number and took the ↓ affordance
     with it. min-width:0 lets it shrink; the filename truncates instead of the button vanishing.
     The primary action must survive, whatever the filename is called. */
  .dl-get { min-width: 0; }
  .dl-file { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .dl-arrow { flex: 0 0 auto; }
}
/* Swap link inside the dialog — a control, not prose. */
.dl-swap { background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--blue-bright); font: inherit; text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--blue-bright) 42%, transparent); }
.dl-swap:hover { text-decoration-color: var(--blue-bright); }
.dl-facts { display: grid; gap: 0; margin: var(--s-4) 0 0; }
.dl-facts > div { display: grid; grid-template-columns: 110px 1fr; gap: var(--s-3);
  padding: var(--s-2) 0; border-bottom: 1px solid var(--border); }
.dl-facts dt { color: var(--text-3); font-family: var(--mono); font-size: var(--t-eyebrow);
  letter-spacing: .06em; text-transform: uppercase; }
.dl-facts dd { color: var(--text); font-size: var(--t-body-s); margin: 0; }

/* ── DOWNLOADS MODAL ───────────────────────────────────────────────────────────────────────────
   Injected by downloads.js on every tab, using the site's own .modal shell so it is not a second
   dialog language. */
.dl-modal .modal-card { width: min(820px, 100%); }
.dl-modal h3 { font-family: var(--disp); font-weight: 500; font-size: clamp(20px, 2.4vw, 27px);
  letter-spacing: -.015em; color: var(--white); margin: 0 0 var(--s-2); }
.dl-modal h4 { font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-3); margin: var(--s-5) 0 var(--s-2); }
.dl-lede { color: var(--text-2); font-size: var(--t-body); line-height: 1.6; margin: 0; max-width: 68ch; }
.dl-tablewrap { overflow-x: auto; }
.dl-table { width: 100%; border-collapse: collapse; font-size: var(--t-body-s); }
.dl-table td { padding: var(--s-2) var(--s-2) var(--s-2) 0; border-bottom: 1px solid var(--border);
  vertical-align: top; color: var(--text-2); line-height: 1.5; }
/* These two are for the BUNDLE table, whose columns are file · size · description. The field key
   reuses .dl-table but its second column is prose, so it must not inherit `nowrap` and mono —
   it did, and every field description was cut off mid-word at the modal edge ("go there for
   anythi", "EACH CARRYING ITS OWN EV"). Scoped away from .dl-fields. */
.dl-table:not(.dl-fields) td:first-child { white-space: nowrap; color: var(--text); }
.dl-table:not(.dl-fields) td:nth-child(2) { white-space: nowrap; color: var(--text-3); font-family: var(--mono); }
.dl-table code { font-family: var(--mono); font-size: .95em; color: var(--text); }
.dl-table tr.dl-mine td { background: var(--blue-soft); }
.dl-src { margin: 0; padding-left: var(--s-4); color: var(--text-2); font-size: var(--t-body-s); line-height: 1.6; }
.dl-src li { margin-bottom: var(--s-2); }
.dl-src code { font-family: var(--mono); color: var(--text); }
.dl-foot { margin: var(--s-5) 0 0; font-size: var(--t-body-s); }


/* ── DOCUMENT READER ───────────────────────────────────────────────────────────────────────────
   Resources renders its markdown here instead of linking at the raw .md — see docmodal.js.
   The measure is the point: these documents run to 2,000 lines and are meant to be READ. */
.doc-modal .modal-card { width: min(940px, 100%); }
.doc-modal h3 { font-family: var(--disp); font-weight: 500; font-size: clamp(20px, 2.4vw, 27px);
  letter-spacing: -.015em; color: var(--white); margin: 0 0 var(--s-1); }
.doc-meta { margin: 0 0 var(--s-4); font-family: var(--mono); font-size: var(--t-eyebrow); }
.doc-loading { color: var(--text-3); font-family: var(--mono); font-size: var(--t-body-s); }
.doc-body { max-width: 74ch; color: var(--text-2); font-size: var(--t-body); line-height: 1.7; }
.doc-body h1, .doc-body h2, .doc-body h3, .doc-body h4, .doc-body h5, .doc-body h6 {
  font-family: var(--disp); font-weight: 500; color: var(--white);
  letter-spacing: -.012em; line-height: 1.25; margin: 1.8em 0 .5em;
}
.doc-body h1 { font-size: 25px; } .doc-body h2 { font-size: 21px; }
.doc-body h3 { font-size: var(--t-h3); } .doc-body h4 { font-size: var(--t-body); }
.doc-body h5, .doc-body h6 { font-size: var(--t-body-s); color: var(--text-3);
  font-family: var(--mono); text-transform: uppercase; letter-spacing: .06em; }
.doc-body > *:first-child { margin-top: 0; }
.doc-body p { margin: 0 0 1em; }
.doc-body ul, .doc-body ol { margin: 0 0 1em 1.2em; }
.doc-body li { margin-bottom: .4em; }
.doc-body strong { color: var(--text); font-weight: 600; }
.doc-body a { color: var(--blue-bright); }
.doc-body code { font-family: var(--mono); font-size: .88em; color: var(--text);
  background: var(--surface-2); border-radius: var(--r-s); padding: var(--s-05) var(--s-1); }
.doc-body pre { background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-m); padding: var(--s-3) var(--s-4); overflow-x: auto; margin: 0 0 1.2em; }
.doc-body pre code { background: none; padding: 0; font-size: var(--t-micro); line-height: 1.6;
  color: var(--text-2); }
.doc-body blockquote { margin: 0 0 1.2em; padding: var(--s-05) 0 var(--s-05) var(--s-4);
  border-left: 2px solid var(--blue); color: var(--text); }
.doc-body hr { border: 0; border-top: 1px solid var(--border); margin: 1.8em 0; }
.doc-tablewrap { overflow-x: auto; margin: 0 0 1.2em; }
.doc-body table { border-collapse: collapse; font-size: var(--t-body-s); min-width: 100%; }
.doc-body th, .doc-body td { padding: var(--s-2) var(--s-3) var(--s-2) 0; border-bottom: 1px solid var(--border);
  text-align: left; vertical-align: top; }
.doc-body th { color: var(--text); font-family: var(--mono); font-size: var(--t-eyebrow);
  letter-spacing: .06em; text-transform: uppercase; }

/* `read the document` is a <button> now, so it needs a surface for the same reason .chip did —
   an unstyled button takes the UA's grey slab. */
.res .open { background: none; border: 0; padding: 0; cursor: pointer; font: inherit; }
.count-sub { margin: var(--s-05) 0 var(--s-1); color: var(--text-2); font-size: var(--t-body-s);
  line-height: 1.6; max-width: 68ch; }


/* ── SECONDARY TABS ────────────────────────────────────────────────────────────────────────────
   LA: "move them to right most on tabs, and dim them a bit."

   Pairs and Skeletons are rightmost and quieter than the rest. This is a judgement about
   CONFIDENCE, not about correctness, and the tabs say so in their own words:

     Pairs      — the right format and too few of them. 47 after deduping by brand.
     Skeletons  — 324 sections across 52 pages expressed in a three-shape vocabulary
                  (stage/split/statement), so it does not deliver the "same layout" it promised.
                  Palette, type scale, motion timing and the art-direction brief do transfer,
                  and that is what it now claims.

   Dimmed, never hidden and never disabled: a corpus that quietly buries its weakest work is
   not the corpus this project argues for. Full strength on hover, focus and when active, so the
   de-emphasis is a resting state and never an obstacle. */
.tab-2 { opacity: .58; }
.tab-2:hover, .tab-2:focus-visible, .tab-2.on { opacity: 1; }
/* The dimming must not take the label under AA. --text-2 at 58% on --bg computes below 4.5:1,
   so the resting colour is lifted to compensate rather than the opacity being the whole effect. */
.tab-2:not(.on) { color: var(--text); }


/* ── FOLD, CLOSED ──────────────────────────────────────────────────────────────────────────────
   RESTORED. These rules were written once and then deleted by my own edit: rewriting the download
   bar replaced everything between `.dl-bar {` and the DOWNLOADS MODAL comment, and this block sat
   between them. Measured live afterwards, the closed fold was still 643px wide at radius 12 — the
   rounded rectangle LA called "ugly and inconsistent", silently reinstated. Second time this
   session that slicing a range by two markers has taken out a neighbour; measure the result, do
   not assume the edit was surgical.

   A CLOSED FOLD IS A CONTROL, NOT A CONTAINER — it shrinks to its label and takes the same pill
   shape as the download controls beside it. Open, it becomes a panel with a reading measure. */
.tab-intro .fold:not([open]), .hero .fold:not([open]) {
  width: fit-content;
  border-radius: var(--r-pill);
}
.tab-intro .fold:not([open]) > summary, .hero .fold:not([open]) > summary {
  border-radius: var(--r-pill);
}
.tab-intro .fold[open], .hero .fold[open] { border-radius: var(--r-l); }
.tab-intro .fold > summary, .hero .fold > summary { padding: 0 var(--s-4); min-height: 44px; }


/* ── PROVENANCE STEPS ──────────────────────────────────────────────────────────────────────────
   Pretty 20 states its own pipeline in the open: captured, rebuilt, described. LA asked whether a
   reader could tell what they were looking at — they could not, and the answer belongs above the
   fold rather than inside a disclosure. Numbered because the order is the point. */
.steps { list-style: none; counter-reset: s; margin: var(--s-4) 0 0; padding: 0;
  display: grid; gap: var(--s-2); max-width: 78ch; }
.steps li { counter-increment: s; position: relative; padding-left: var(--s-8);
  color: var(--text-2); font-size: var(--t-body-s); line-height: 1.6; }
.steps li::before {
  content: counter(s); position: absolute; left: 0; top: 1px;
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-3); font-family: var(--mono); font-size: 10px;
}
.steps b { color: var(--text); font-weight: 600; }
.steps code { font-family: var(--mono); font-size: .92em; color: var(--blue-bright);
  background: var(--blue-soft); border-radius: var(--r-s); padding: var(--s-05) var(--s-1); }


/* ── THE METHODOLOGY BADGE IS GONE ─────────────────────────────────────────────────────────────
   LA: "You added methodology which I did not want and broke the controls."
   Removed in full — v5-src/methodology.js is in the Trash, index.html no longer loads it, and
   tools/build-v5.mjs no longer copies it. `.meth-btn`, `.meth-dot`, `.meth-btn { order: -1 }`,
   `.meth-modal`, `.meth-what`, `.meth-steps`, `.meth-split`, `.meth-trust`, `.meth-limits`,
   `.meth-serves`, `.meth-serve` and `.meth-who` went with it: rules for a control that can no
   longer exist are how a stylesheet starts describing a site that is not there.

   `.meth-kicker` SURVIVES and is deliberately not renamed. downloads.js prints it above the data
   preview ("PREVIEW") and tools/build-motion-page.mjs uses it too, so it is a live class with a
   fossil name, not a leftover. */
.meth-kicker { margin: 0 0 var(--s-1); font-family: var(--mono); font-size: var(--t-eyebrow);
  letter-spacing: .1em; text-transform: uppercase; color: var(--blue-bright); }


/* ── DATA PREVIEW ──────────────────────────────────────────────────────────────────────────────
   LA: "it should say preview and it should be a pill within the label… this needs to feel like
   GitHub with our Google theme, and be really clear for engineers that are most busy."

   So: a dense, monospaced, scannable register — a field key, a real record, and provenance —
   rather than prose. A busy engineer should be able to decide in ten seconds whether this file is
   worth their time. */
.dl-peek { padding: 0 var(--s-2) 0 var(--s-2); }
.dl-peek-pill {
  display: inline-flex; align-items: center; padding: var(--s-05) var(--s-2);
  border-radius: var(--r-pill); background: var(--surface-2);
  border: 1px solid var(--border); color: var(--text-3);
  font-family: var(--mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
}
.dl-peek:hover .dl-peek-pill { background: var(--blue-soft); border-color: var(--blue-border);
  color: var(--blue-bright); }

.dl-modal h3 code { font-family: var(--mono); font-size: .82em; color: var(--white);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-s); padding: var(--s-05) var(--s-2); }
.dl-collected { margin: 0; color: var(--text-2); font-size: var(--t-body-s); line-height: 1.65;
  max-width: 74ch; }
.dl-meta { color: var(--text-3); font-family: var(--mono); font-size: var(--t-eyebrow);
  margin-right: 1ch; }

/* The code block. Bordered and headed like a file view, because that is what it is. */
.dl-code { border: 1px solid var(--border); border-radius: var(--r-m); overflow: hidden;
  background: var(--surface-2); }
.dl-code-head { display: flex; justify-content: space-between; align-items: center; gap: var(--s-3);
  padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--border);
  font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-3); background: var(--surface); }
.dl-code-n { color: var(--blue-bright); }
.dl-code pre { margin: 0; padding: var(--s-3) var(--s-4); overflow-x: auto; }
.dl-code code { font-family: var(--mono); font-size: var(--t-micro); line-height: 1.65;
  color: var(--text-2); white-space: pre; }

/* Field key: monospace name, plain-language meaning. */
.dl-fields { table-layout: auto; width: 100%; }
.dl-fields td:first-child { width: 1%; padding-right: var(--s-4); vertical-align: top; white-space: nowrap; }
.dl-fields td:last-child { white-space: normal; font-family: var(--sans); line-height: 1.6; }
.dl-fields td:first-child code { font-family: var(--mono); font-size: var(--t-micro);
  color: var(--blue-bright); white-space: nowrap; }
.dl-fields td:last-child { color: var(--text-2); }
.dl-prov { display: block; margin-top: var(--s-1); color: var(--text-3); font-size: var(--t-micro);
  line-height: 1.5; }


/* ── PROVENANCE, AND THE PROMPT ────────────────────────────────────────────────────────────────
   LA: "make more clear in Pretty 38 modals that those come from Gemini, and maybe have a label
   that when clicked shows the prompt we gave Gemini." The model name was one grey pill among four
   and said nothing about what it had done. This is a stated line above the description, with the
   exact prompt one click away. */
.p20-prov { border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
  padding: var(--s-3) 0; margin: 0 0 var(--s-5); }
.p20-prov-line { margin: 0; color: var(--text-2); font-size: var(--t-body-s); line-height: 1.6;
  max-width: 74ch; }
.p20-prov-line b { color: var(--text); }
.p20-prov-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--blue-bright); margin-right: .7ch; vertical-align: middle; }
.p20-prompt-btn { margin-top: var(--s-2); background: var(--surface-2); cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  padding: var(--s-05) var(--s-3); min-height: 32px;
  font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .04em;
  color: var(--text-2); }
.p20-prompt-btn:hover { color: var(--text); border-color: var(--text-3);
  background: var(--surface-elev); }
.p20-prompt { margin-top: var(--s-3); }
.p20-prompt h4 { font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-3); margin: var(--s-4) 0 var(--s-2); }
.p20-prompt-note { margin: 0 0 var(--s-2); color: var(--text-3); font-size: var(--t-micro);
  line-height: 1.55; max-width: 74ch; }
.p20-prompt pre { background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--r-m); padding: var(--s-3) var(--s-4); overflow-x: auto; margin: 0; }
.p20-prompt code { font-family: var(--mono); font-size: var(--t-micro); line-height: 1.65;
  color: var(--text-2); white-space: pre-wrap; word-break: break-word; }

/* The "who each tab is for" grid went with the methodology modal it lived inside — see the note
   where .meth-btn used to be. */


/* ── REGISTER, AND ITS STEERING BRIEF ──────────────────────────────────────────────────────────
   The atmosphere a page sits in, plus the directive brief derived from it. Two forms of the same
   knowledge: the description says what THIS page looks like, the brief says how to make another
   that feels like it. Built by tools/build-registers.mjs. */
/* AN EDGE, NOT A FILL. This is an aside about a taxonomy, and it was the largest saturated surface
   in the opening viewport of every modal — 247px of it on a phone, before the reader has seen the
   site the record is about. Worse, it was the FIRST blue fill anyone met, which taught them to read
   that treatment as "metadata"; then the whole-page block arrived wearing the same one. Blue fill
   now means our claim, and nothing else. */
.p20-reg { border: 1px solid var(--border); border-left: 2px solid var(--blue-border);
  background: transparent;
  border-radius: var(--r-l); padding: var(--s-3) var(--s-4); margin: 0 0 var(--s-4); }
.p20-reg-head { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; }
.p20-reg-chip { font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .06em;
  text-transform: uppercase; color: var(--blue-bright); border: 1px solid var(--blue-border);
  border-radius: var(--r-pill); padding: var(--s-05) var(--s-2); white-space: nowrap; }
.p20-reg-def { margin: 0; flex: 1 1 320px; color: var(--text); font-size: var(--t-body-s);
  line-height: 1.55; }
.p20-reg-note { margin: var(--s-2) 0 0; color: var(--text-2); font-size: var(--t-micro);
  line-height: 1.6; max-width: 74ch; }
.p20-reg-note b { color: var(--text); }
.p20-reg-steer { margin-top: var(--s-3); }
.p20-reg-sum { margin: 0 0 var(--s-2); color: var(--text); font-size: var(--t-body-s);
  line-height: 1.6; max-width: 74ch; }
.p20-reg-steer h4 { font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .07em;
  text-transform: uppercase; color: var(--text-3); margin: var(--s-3) 0 var(--s-1); }
.p20-reg-steer ul { margin: 0 0 0 var(--s-4); color: var(--text-2); font-size: var(--t-body-s);
  line-height: 1.6; }
.p20-reg-steer li { margin-bottom: var(--s-1); }
.p20-reg-tell { margin: var(--s-3) 0 0; padding-top: var(--s-2);
  border-top: 1px solid var(--blue-border); color: var(--text);
  font-size: var(--t-body-s); line-height: 1.6; }

/* The separation bars carry their dimension name, not just a three-letter code. Found by a vision
   critique of this page; no programmatic check can see that a label means nothing to a reader. */
.f-bar { grid-template-columns: 42px minmax(0,1fr) 150px 96px !important; }
.f-what { color: var(--text-2); font-size: var(--t-micro); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.f-num { font-family: var(--mono); font-size: var(--t-micro); color: var(--text-2);
  white-space: nowrap; text-align: right; }
.f-num i { font-style: normal; opacity: .5; margin: 0 .5ch; }
.f-key { font-size: var(--t-micro) !important; color: var(--text-3) !important;
  line-height: 1.6; margin: 0 0 var(--s-2) !important; max-width: 74ch; }
.f-key b { color: var(--text-2); }
@media (max-width: 720px) { .f-bar { grid-template-columns: 40px 1fr 84px !important; }
  .f-what { display: none; } }

/* The transfer line leads the skeleton card: which design language moved onto which unrelated
   business. Found by a vision critique — the screenshot's own headline was outshouting the only
   thing on the card that says what the experiment is. */
.xfer { display: flex; align-items: baseline; gap: .6ch; flex-wrap: wrap;
  font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .03em;
  margin-bottom: var(--s-1); }
.xfer-a { color: var(--text-3); }
.xfer-arrow { color: var(--blue-bright); }
.xfer-b { color: var(--text); }


/* ── TEARDOWN: THE SOURCE SECTION AND WHAT WE BUILT FROM IT ────────────────────────────────────
   LA: "the screenshots must be of the components/section actually translated to React and rendered
   of it." Until now the record showed the original page and an account of it, and the React was
   cited by name and never seen — which is a description, not a teardown. The pairing IS the
   artifact: this section, this code, this render. */
.td-react { margin: var(--s-5) 0 0; }
.td-react h3 { font-family: var(--disp); font-weight: 500; font-size: var(--t-h3);
  color: var(--white); margin: 0 0 var(--s-1); }
.td-react-note { margin: 0 0 var(--s-3); color: var(--text-2); font-size: var(--t-body-s);
  line-height: 1.6; max-width: 74ch; }
.td-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3);
  margin: 0 0 var(--s-4); }
@media (max-width: 760px) { .td-pair { grid-template-columns: 1fr; } }
.td-shot { display: flex; flex-direction: column;
  border: 1px solid var(--border); border-radius: var(--r-m); overflow: hidden;
  background: var(--surface-2); }
.td-shot img { display: block; width: 100%; height: auto; background: var(--surface-3); }
/* THE LABEL GOES ABOVE THE PICTURE, AND IT STAYS. Below the image you form a judgement and are
   then told whose work it was — backwards, and on a phone worse than backwards: under 760px the
   pair stacks, adjacency disappears, and two full-bleed pictures separated by an 11px whisper are
   no longer legible as a comparison at all. Sticky is what saves the stacked case: the label rides
   the top of the viewport while its own image scrolls under it. Mistaking the source for our work
   is the single worst failure this page can have. */
.td-shot figcaption { order: -1; position: sticky; top: 0; z-index: 1;
  padding: var(--s-2) var(--s-3); font-family: var(--mono);
  font-size: var(--t-micro); letter-spacing: .08em; text-transform: uppercase; font-weight: 500;
  color: var(--text-2); background: var(--surface-3);
  border-bottom: 1px solid var(--border); }
.td-shot code { color: var(--text-2); text-transform: none; letter-spacing: 0; }
/* Ours carries a stated surface, not a hue on a hairline at 42% alpha. */
.td-ours { border-color: var(--blue-border); box-shadow: inset 0 0 0 1px var(--blue-border); }
.td-ours figcaption { background: var(--blue-soft); color: var(--blue-bright); font-weight: 600;
  border-bottom-color: var(--blue-border); }
.td-ours figcaption span { color: var(--blue-bright); }


/* ── THE WHOLE PAGE ────────────────────────────────────────────────────────────────────────────
   The section-level pairing above proves a band was re-authored. This proves the experience
   survived being put back together, which is the question the team lead actually asked, so it sits ABOVE
   the component pairs and is the only element in the modal with a filled button. */
.td-page { margin: var(--s-5) 0 0; padding: var(--s-5); border-radius: var(--r-l);
  background: var(--blue-soft); border: 1px solid var(--blue); }
/* Larger than .td-react's h3 on purpose. Both were 17px/500, so the whole-page block led on fill
   alone — and lost that lead 200px later when the component pairs arrived, because a photograph
   beats a tinted rectangle every time. */
.td-page h3 { font-family: var(--disp); font-weight: 600; font-size: var(--t-h2);
  color: var(--white); margin: 0 0 var(--s-1); }
/* 62ch, not 74: inside a filled panel the leftover measure reads as 165px of empty blue, which
   plain prose on a transparent ground never shows. */
.td-page p { margin: 0 0 var(--s-3); color: var(--text-2); font-size: var(--t-body-s);
  line-height: 1.6; max-width: 62ch; }
/* --blue-fill, not --blue: the comment at the top of this file records why #fff on --blue is
   3.03:1 and --blue-fill exists. This button had #06131f on --blue, and lost even that to
   `.modal-body a` at higher specificity, so the primary action of the whole tab rendered
   #6BB6FF on #2E96FF — 1.41:1. The most important control on the page was a ghost on its own
   fill, in both themes. The .modal-body override below is what actually makes the ink stick. */
.td-page-btn { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--s-4);
  border-radius: var(--r-pill); background: var(--blue-fill); color: #fff; text-decoration: none;
  font-weight: 600; font-size: var(--t-body-s); }
.td-page-btn:hover { background: var(--blue); }
.td-page-btn:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 3px; }
.modal-body a.td-page-btn, .modal-body a.td-page-btn:hover { color: #fff; text-decoration: none; }
/* The project download sits beside the page, not under it: they are the two halves of the same
   claim — here is the result, and here is the code that produces it. Outlined so the page stays
   the single filled action. */
.td-page-row { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.td-page-alt { display: inline-flex; align-items: center; min-height: 44px; padding: 0 var(--s-4);
  border-radius: var(--r-pill); background: transparent; border: 1px solid var(--blue-border);
  color: var(--blue-bright); font-weight: 600; font-size: var(--t-body-s); text-decoration: none; }
.td-page-alt:hover { background: rgba(46,150,255,.14); }
.modal-body a.td-page-alt { color: var(--blue-bright); text-decoration: none; }
.td-page-what { margin: var(--s-3) 0 0 !important; color: var(--text-2); font-size: var(--t-body-s);
  line-height: 1.6; max-width: 68ch; }
.td-page-what code { font-family: var(--mono); font-size: .92em; color: var(--text); }

/* ── SALIENCY AND THE COUNTERFACTUAL ───────────────────────────────────────────────────────────
   Two fields added to the description prompt after a vision critique of this very site showed that
   scan path and failure modes are where a model's eye adds most — and that neither can be
   recovered from markup. `saliency` is the ranked scan path; `would_break_if` names the specific
   changes that would stop the design working, which is the anti-slop signal in positive form. */
.td-sal { margin: 0; padding-left: 0; list-style: none; counter-reset: sal; display: grid;
  gap: var(--s-2); }
.td-sal li { counter-increment: sal; position: relative; padding-left: var(--s-8); }
.td-sal li::before { content: counter(sal); position: absolute; left: 0; top: 0;
  width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--blue-soft); border: 1px solid var(--blue-border);
  color: var(--blue-bright); font-family: var(--mono); font-size: 10px; }
.td-sal b, .td-break b { display: block; color: var(--text); font-weight: 500;
  font-size: var(--t-body-s); line-height: 1.5; }
.td-sal span, .td-break span { display: block; margin-top: 2px; color: var(--text-3);
  font-size: var(--t-micro); line-height: 1.55; }
.td-break-note { margin: 0 0 var(--s-2); color: var(--text-3); font-size: var(--t-micro);
  line-height: 1.6; max-width: 74ch; }
.td-break { margin: 0; padding-left: 0; list-style: none; display: grid; gap: var(--s-2); }
.td-break li { padding-left: var(--s-4); border-left: 2px solid var(--sig-warn); }

/* A full teardown has all three layers; a described-only record has two. Marked on the card,
   because the difference is the whole claim and a training team needs to filter on it. */
.chip-full { background: var(--blue-soft); border-color: var(--blue-border);
  color: var(--blue-bright); }
.hero-note { margin: var(--s-2) 0 0; color: var(--text-2); font-size: var(--t-body-s);
  line-height: 1.6; max-width: 74ch; }
.hero-note b { color: var(--text); }

/* A render is not a link. Each teardown's React can be opened and run, not only looked at. */
.td-open { display: inline-block; margin-left: 1ch; color: var(--blue-bright); }

/* Open the component and run it, not just look at a render of it. */
.td-actions { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--s-3);
  flex-wrap: wrap; margin-top: var(--s-1); }
/* OUTLINE, NOT FILL. There is one filled button per modal and it is the whole page. Two filled
   pills made "open one section" tie with "open the entire translated site", which is exactly
   backwards. */
.td-btn { display: inline-flex; align-items: center; min-height: 40px; padding: 0 var(--s-4);
  border-radius: var(--r-pill); background: transparent; border: 1px solid var(--blue-border);
  color: var(--blue-bright);
  font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .04em;
  text-decoration: none; }
.td-btn:hover { background: var(--blue-soft); }
.modal-body a.td-btn { color: var(--blue-bright); text-decoration: none; }
.td-actions-note { color: var(--text-3); font-size: var(--t-micro); }
.td-actions-note code { font-family: var(--mono); color: var(--text-2); }

/* ── THE PROMPT MAP, THE GAPS, AND THE DISAGREEMENTS ───────────────────────────────────────────
   Three tables that answer the question a reviewer arrives with — what can I rely on you for —
   and the two that follow from it: what you do not cover, and where your own sources disagree.
   Tiered by colour so the answer is legible before a word is read: green earns confidence, amber
   is a caveat, red is an admission. */
.pm-tier { display: flex; align-items: baseline; gap: var(--s-2); margin: var(--s-5) 0 var(--s-1);
  font-family: var(--disp); font-size: var(--t-h3); font-weight: 500; color: var(--white); }
.pm-tier span { font-family: var(--mono); font-size: var(--t-eyebrow); letter-spacing: .06em;
  padding: 2px 9px; border-radius: var(--r-pill); border: 1px solid var(--border);
  color: var(--text-2); background: var(--surface-2); }
.pm-strong span { color: var(--green); border-color: var(--green-border, rgba(91,209,139,.35)); background: var(--green-soft); }
.pm-thin   span { color: var(--amber, #f9ab00); border-color: rgba(249,171,0,.35); background: var(--amber-soft); }
.pm-none   span { color: var(--red, #f0a0a0); border-color: rgba(220,80,80,.35); background: var(--red-soft); }
.pm-note { margin: 0 0 var(--s-3); color: var(--text-2); font-size: var(--t-body-s); max-width: 82ch; }

.cx-tablewrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--r-l);
  background: var(--surface); margin: 0 0 var(--s-4); }
.pm-table { width: 100%; border-collapse: collapse; font-size: var(--t-body-s); min-width: 720px; }
.pm-table th { text-align: left; padding: var(--s-2) var(--s-3); font-family: var(--mono);
  font-size: var(--t-micro); letter-spacing: .08em; text-transform: uppercase; color: var(--text-3);
  border-bottom: 1px solid var(--border); font-weight: 500; white-space: nowrap; }
.pm-table td { padding: var(--s-3); vertical-align: top; border-bottom: 1px solid var(--border);
  color: var(--text-2); line-height: 1.55; }
.pm-table tr:last-child td { border-bottom: 0; }
.pm-table tbody tr:nth-child(even) { background: var(--surface-2); }
.pm-prompt { color: var(--text); font-weight: 500; min-width: 20ch; }
.pm-prompt a { color: var(--blue-bright); }
.pm-eg { display: block; margin-top: 3px; font-family: var(--mono); font-size: var(--t-micro);
  color: var(--text-3); letter-spacing: .02em; font-weight: 400; }
.pm-n { font-family: var(--mono); text-align: right; white-space: nowrap; color: var(--text); }
.pm-missing { max-width: 46ch; }
.pm-none-cell, .pm-none { color: var(--text-3); }
.pm-table code { font-family: var(--mono); font-size: .92em; color: var(--blue-bright); }

.cx-deferred { list-style: none; margin: 0 0 var(--s-4); padding: 0;
  display: grid; gap: var(--s-1); }
.cx-deferred li { display: grid; grid-template-columns: minmax(14ch, 22ch) 9ch 1fr; gap: var(--s-3);
  align-items: baseline; padding: var(--s-2) var(--s-3); border: 1px solid var(--border);
  border-radius: var(--r-m); background: var(--surface); color: var(--text-2);
  font-size: var(--t-body-s); line-height: 1.5; }
.cx-deferred li b { color: var(--text); font-weight: 500; }
.cx-lede-dim { color: var(--text-3); font-family: var(--mono); font-size: var(--t-micro); letter-spacing: .02em; }
@media (max-width: 700px) {
  .cx-deferred li { grid-template-columns: 1fr; gap: 2px; }
  .pm-tier { font-size: var(--t-body); }
}
.pm-tag { display: inline-block; font-family: var(--mono); font-size: var(--t-micro);
  letter-spacing: .06em; padding: 1px 8px; border-radius: var(--r-pill); border: 1px solid var(--border); }
.pm-tag.pm-strong { color: var(--green); border-color: rgba(91,209,139,.35); background: var(--green-soft); }
.pm-tag.pm-thin { color: var(--amber, #f9ab00); border-color: rgba(249,171,0,.35); background: var(--amber-soft); }
.skill-dl { display: inline-flex; align-items: center; min-height: 40px; padding: 0 var(--s-4);
  border-radius: var(--r-pill); background: var(--blue-fill); color: #fff !important;
  text-decoration: none !important; font-weight: 600; font-size: var(--t-body-s); }
.skill-dl:hover { background: var(--blue); }

/* ── THE VOCABULARY, ATTACHED TO THE WORD ──────────────────────────────────────────────────────
   A chip reading "evergreen" is a term of art with no key anywhere on the site, and it reads as
   praise when it means something purely factual. Any chip the glossary knows becomes pressable —
   marked with a dotted underline rather than a new colour, because these sit inside dense rows
   where another accent would compete with the row's own meaning. */
.gl-term { cursor: help; text-decoration: underline dotted currentColor 1px; text-underline-offset: 3px; }
.gl-term:hover { border-color: var(--text-3); }
.gl-term:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 2px; }
.gl-link { margin-left: auto; font-size: var(--t-micro); font-family: var(--mono);
  letter-spacing: .04em; color: var(--text-3); text-decoration: none; align-self: center; }
.gl-link:hover { color: var(--text-2); text-decoration: underline; }
.gl-modal { position: fixed; inset: 0; z-index: 120; display: flex; align-items: center;
  justify-content: center; padding: var(--s-4); }
.gl-modal[hidden] { display: none; }
/* Desktop-only, for the same reason as .modal-scrim. */
.gl-scrim { position: absolute; inset: 0; background: var(--scrim); }
@media (min-width: 768px) {
  .gl-scrim { -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
}
.gl-card { position: relative; max-width: 560px; width: 100%; max-height: 82vh; overflow: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-l);
  padding: var(--s-5) var(--s-5) var(--s-4); box-shadow: 0 24px 64px rgba(0,0,0,.5); }
.gl-x { position: absolute; top: var(--s-3); right: var(--s-3); background: none; border: 0;
  color: var(--text-3); font-size: 16px; cursor: pointer; line-height: 1; }
.gl-x:hover { color: var(--text); }
.gl-where { margin: 0 0 var(--s-1); font-family: var(--mono); font-size: var(--t-micro);
  letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); }
.gl-card h3 { margin: 0 0 var(--s-2); font-family: var(--disp); font-size: var(--t-h3);
  font-weight: 600; color: var(--white); }
.gl-means { margin: 0 0 var(--s-3); color: var(--text); font-size: var(--t-body-s); line-height: 1.65; }
.gl-not { margin: 0 0 var(--s-3); color: var(--text-2); font-size: var(--t-body-s); line-height: 1.65;
  border-left: 2px solid var(--amber, #f9ab00); padding-left: var(--s-3); }
.gl-vals { list-style: none; margin: 0 0 var(--s-3); padding: 0; display: grid; gap: 2px; }
.gl-vals li { display: flex; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-1) var(--s-2); border-radius: var(--r-s); background: var(--surface-2);
  font-size: var(--t-body-s); color: var(--text-2); }
.gl-vals b { font-family: var(--mono); color: var(--text); font-weight: 500; }
.gl-foot { margin: 0; font-size: var(--t-micro); color: var(--text-3); }
.gl-foot a { color: var(--blue-bright); }


/* ══ AUDIT REPAIRS ═════════════════════════════════════════════════════════════════════════════
   A design audit measured every one of these in a browser, in both themes. They are grouped here
   rather than edited in place because several correct the same mistake in different selectors, and
   seeing them together is the only way to notice that. */

/* 1 · THE SAME CONTRAST BUG, IN THE THREE PLACES IT WAS NOT FIXED.
   `.modal-body a:not(.chip)` outranks any colour a button sets, so a filled blue button renders
   #6BB6FF on #2E96FF — 1.41:1. That was found and fixed for .td-page-btn and nowhere else, while
   .modal-visit — "Visit the live site" — carries it in all 739 modals on the Design tab. The gate's
   Enter button, the first control anyone touches, is 3.03:1 at rest and 2.15:1 on hover. And two
   compliant buttons DEGRADE on hover, which is the worst version: correct until you point at it.
   --blue-fill exists precisely because #fff on --blue is 3.03:1. */
.modal-body a.modal-visit, .modal-body a.modal-visit:hover { color: #fff; text-decoration: none; }
.modal-visit { background: var(--blue-fill); }
.modal-visit:hover { background: var(--blue); }
.gate button, .done-btn { background: var(--blue-fill); }
.gate button:hover, .done-btn:hover { background: var(--blue); }
.td-page-btn:hover, .skill-dl:hover { background: var(--blue); }

/* 2 · ELEVATION. Every card on the site is a flat rectangle separated from its page by 1.08:1 of
   luminance and a 1.30:1 hairline, and the 3px hover lift therefore lifts off nothing. Dark gets a
   surface step because a shadow does nothing there; light gets the shadow. */
:root { --shadow-rest: 0 1px 2px rgba(0,0,0,.05), 0 4px 14px -6px rgba(0,0,0,.08);
        --shadow-hover: 0 2px 4px rgba(0,0,0,.06), 0 12px 28px -10px rgba(0,0,0,.14); }
.card, .taxo, .res, .psrc-list li, .cx-tablewrap, .cx-deferred li, .gl-card { box-shadow: var(--shadow-rest); }
.card:hover { box-shadow: var(--shadow-hover); }
:root { --surface: #202126; --border: rgba(255,255,255,0.14); }
:root[data-theme="light"] { --border: rgba(0,0,0,0.16); --surface-2: #f4f4f6; --green: #146a42; }

/* 3 · THE STAT LINE THAT DRAGS THE PAGE SIDEWAYS. `.hit-count { flex: 0 0 auto }` cannot shrink, so
   the moment its string grew from "739" to "100 sites · 37 whole pages in react · 74 with code" the
   Teardowns tab gained 55px of horizontal page drag at 375. The string got longer today; the rule
   has been waiting for it. */
@media (max-width: 720px) {
  .hit-count { flex: 1 1 100%; min-width: 0; white-space: normal; }
}

/* 4 · THE TABLES, MADE PART OF THE SYSTEM. They scroll horizontally at 768 and clipped their last
   column mid-sentence with no hint — the tab strip already solves this with an edge mask and was
   not consulted. The status colours were hardcoded dark-theme rgba, so none of them flipped. */
.cx-tablewrap { -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent);
  mask-image: linear-gradient(90deg, #000 calc(100% - 24px), transparent); }
.cx-tablewrap.at-end, .cx-tablewrap:not(:hover) { -webkit-mask-image: none; mask-image: none; }
.pm-table th { font-size: var(--t-eyebrow); }
.pm-table code { font-size: var(--t-micro); color: var(--text); background: var(--surface-2);
  padding: 1px 5px; border-radius: var(--r-s); }
.pm-strong span { color: var(--green); border-color: color-mix(in srgb, var(--green) 35%, transparent);
  background: color-mix(in srgb, var(--green) 13%, transparent); }
.pm-thin span, .pm-tag.pm-thin { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 35%, transparent);
  background: color-mix(in srgb, var(--amber) 13%, transparent); }
.pm-none span { color: var(--red); border-color: color-mix(in srgb, var(--red) 35%, transparent);
  background: color-mix(in srgb, var(--red) 13%, transparent); }
.pm-tag.pm-strong { color: var(--green); border-color: color-mix(in srgb, var(--green) 35%, transparent);
  background: color-mix(in srgb, var(--green) 13%, transparent); }
/* Red is the stated meaning of absence; it was on the tier header and grey in the cells that carry
   the actual zero. */
.pm-none-cell, .pm-table td .pm-none { color: var(--red); }

/* 5 · ONE PAGE OPENER. `.hero` opened at 60px and moved with window height; `.tab-intro` opened at
   30.4px and did not — a 30px jump when you crossed between Design and any other tab. And the one
   block whose comment says every title must look the same was itself off the type scale on all
   nine. */
.hero { padding: var(--s-8) clamp(20px, 4vw, 48px) var(--s-6); }
.tab-intro { padding: var(--s-8) clamp(20px, 4vw, 48px) var(--s-6); }
.tab-intro .what { font-size: var(--t-body); }
.tab-intro .why { font-size: var(--t-body-s); color: var(--text-3); opacity: 1; }

/* 6 · THE NAV'S FIRST LABEL SAT 16px INBOARD of every other left edge on the site, because the tab
   box aligns and the tab's own padding does not. */
.tabs-in > .tab:first-child { margin-left: calc(var(--s-3) * -1); }

/* 7 · A MOTION GRAMMAR FOR THE CHROME. Nine transitions across four durations and two easings, no
   token for either — while the translated components on this same site run one measured curve and a
   four-rung ladder. The frame should obey the grammar the exhibits do. */
:root { --ui-ease: cubic-bezier(.2,.7,.2,1); --ui-fast: .15s; --ui-base: .22s; }
/* No pressed state existed anywhere in 1766 lines. On touch, where hover does not exist, a tap
   acknowledged nothing at all until the modal appeared. */
:where(button, a.tab, .card, .chip, .dl-get, .td-page-btn, .skill-dl, .modal-visit):active {
  transform: scale(.985);
}
@media (prefers-reduced-motion: reduce) {
  :where(button, a.tab, .card, .chip, .dl-get):active { transform: none; }
}
.skill-alt { display: inline-block; margin-left: var(--s-3); font-size: var(--t-body-s);
  color: var(--blue-bright); text-decoration: none; }
.skill-alt:hover { text-decoration: underline; }

/* ── THE LANDING TAB DRAGGED 51px AT 320 ───────────────────────────────────────────────────────
   `.wordmark` measured 338px inside a 320px viewport, because app.js appends the "what's new" pill
   into it. Design is the only tab that gets that pill, which is exactly why Design was the only tab
   that overflowed — and it is the first tab anyone opens. Dragged, the hero read "etty Websites."
   The pill is worth having; it is not worth having on the narrowest phone. */
@media (max-width: 380px) {
  .wordmark { flex-wrap: wrap; min-width: 0; }
  .wm-new { display: none; }
}

/* A component name is one unbroken camelCase token, so at 320 the Components modal dragged 54px
   sideways and the title rendered as "TresmaresCapitalAnnounce". */
#m-body h2, .modal-card h2 { overflow-wrap: anywhere; }
.modal-card { overflow-x: hidden; }

/* The changelog's primary action was white on --blue: 3.03:1, in the theme that is the default. */
.chlog-go { background: var(--blue-fill); }
.chlog-go:hover { background: var(--blue); }

/* Skip links must be visible when focused, and the target must be able to take focus. */
[id="main"]:focus, [id="main"]:focus-visible { outline: none; }
.noshot-fig .noshot { display: flex; align-items: center; justify-content: center; min-height: 160px;
  border: 1px dashed var(--border-strong); border-radius: var(--r-m); background: var(--surface-2);
  color: var(--text-3); font-family: var(--mono); font-size: var(--t-micro); text-align: center;
  padding: var(--s-3); }
.shown-count { margin-left: var(--s-2); font-family: var(--mono); font-size: var(--t-micro);
  color: var(--text-3); letter-spacing: .03em; align-self: center; }
/* The composed pages navigate away; the cards beside them open a modal. A real anchor restores
   cmd-click, middle-click and copy-link, and the arrow says the card leaves the page. */
.card-link { display: block; text-decoration: none; color: inherit; position: relative; }
.card-link::after { content: '↗'; position: absolute; top: var(--s-3); right: var(--s-3);
  font-size: var(--t-micro); color: var(--text-3); background: var(--scrim);
  border-radius: var(--r-pill); padding: 2px 7px; }
.card-link:focus-visible { outline: 2px solid var(--blue-bright); outline-offset: 3px; border-radius: var(--r-m); }
.opts-none { grid-column: 1 / -1; margin: 0; padding: var(--s-3); color: var(--text-3);
  font-size: var(--t-body-s); }
.tx-def { margin: 0 0 var(--s-3); max-width: 74ch; color: var(--text-2);
  font-size: var(--t-body-s); line-height: 1.65; }
.tx-def b { color: var(--text); font-weight: 500; }
/* THE FIRST SCREEN ANYONE SEES said only that the review was gated — true, and silent about what
   is behind it. Someone arriving with a passcode from a colleague should be able to tell in one
   line whether they are in the right place. The gated notice stays; it just stops being the only
   thing said. */
.gate-sub { max-width: 46ch; margin-left: auto; margin-right: auto; line-height: 1.6; }
.gate-gated { display: inline-block; margin-top: var(--s-2); color: var(--text-3);
  font-family: var(--mono); font-size: var(--t-micro); letter-spacing: .04em; }
/* A DATA FAILURE HAS TO BE VISIBLE. Two tabs rendered a completely blank page on a 404 or a
   malformed response — including the one a reviewer opens first. */
.load-fail { margin: var(--s-8) auto; max-width: 52ch; text-align: center; color: var(--text-2);
  font-size: var(--t-body); line-height: 1.7; }
.load-fail button { background: none; border: 0; padding: 0; color: var(--blue-bright);
  font: inherit; text-decoration: underline; cursor: pointer; }
.load-fail span { display: inline-block; margin-top: var(--s-2); font-family: var(--mono);
  font-size: var(--t-micro); color: var(--text-3); }

/* Palette swatch: a bordered box, so a colour is legible against any ground — a tinted glyph is not.
   See the note in skeletons.js; 40 of 108 swatches were invisible in dark and 52 in light. */
.sw { width: 10px; height: 10px; display: inline-block; vertical-align: baseline;
      border: 1px solid var(--border-strong); border-radius: 2px; }

/* The all-terms view: one row per term, so the standing "what these words mean" control answers
   the question it asks instead of downloading a markdown file. */
.gl-row { padding: var(--s-3) 0; border-top: 1px solid var(--border); }
.gl-row b { color: var(--white); font-family: var(--mono); font-size: var(--t-body-s); }
.gl-row-where { margin-left: var(--s-2); font-family: var(--mono); font-size: var(--t-micro);
  color: var(--text-3); }
.gl-row p { margin: var(--s-1) 0 0; font-size: var(--t-body-s); line-height: 1.55; color: var(--text-2); }

/* The topbar reserved 96px for a second line it no longer has: the "what's new" pill sits inline
   in the wordmark and the glossary link moved to the tab rail, so every tab's bar is one row. The
   96px reservation was the band of dead bar LA flagged. */
.topbar { min-height: 64px; }
.gl-link { background: none; border: 0; cursor: pointer; padding: var(--s-1) 0; }
/* In the tab rail: pushed to the right edge, never shrinking into the scrollable strip. */
.tabs-in .gl-link { margin-left: auto; flex: 0 0 auto; align-self: center; }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   THE CONTROL BAR GRAMMAR
   ══════════════════════════════════════════════════════════════════════════════════════════════

   LA: "the control bar has some 'stickers', 'labels', buttons of different kind. Some just say
   something like '739', some are toggles like grid/card, some are filters, or buttons… We need to
   improve all their style, distinguish them well, harmonize." And: "we need to nail that as it is
   something I like a lot creating — a control bar that gives you infos, controls."

   Then, sending his own computer-vision app's top bar as the reference:
   "I think the key is to normalize height and round radius."

   That is the rule, and it is a better one than "make them all pills":

   ── GEOMETRY IS CONSTANT. FILL CARRIES ALL THE MEANING. ──────────────────────────────────────

     Every control in this bar is 46px tall with an --r-s corner. Not one of them is a different
     height, a different radius, or a different shape. Five kinds of thing read as one family
     because their boxes are identical; they stay distinguishable because their FILLS are not.

   What the row used to be, and why LA kept calling it amateur: the count was bare mono text with
   no box at all, the vocabulary's four section links were UNDERLINED TEXT 23px tall, the dice and
   the segment were 36px, and the download was a 46px pill. Four heights, three shapes, and no way
   to tell by looking which of them did anything.

   ── THE FILL LADDER ──────────────────────────────────────────────────────────────────────────

     transparent + --border            AVAILABLE. Press it and something happens, but nothing is
                                       happening now. The row's resting state, and most of it.
     --surface-2 fill, no border       INERT. It states a fact. `739` is a number you read, not a
                                       control you press, and the missing border is what says so.
     --surface-2 fill + --border       OFFERED. The one thing the bar hands you: the dataset.
                                       Monochrome — being offered is not a state, it is a role.
     --blue-soft + --blue-bright       ON. The half of the toggle you are in; the section you are
       + inset rule + weight 600       looking at. This is the only place colour appears.

   ── COLOUR MEANS "ON", AND NOTHING ELSE MEANS ANYTHING ───────────────────────────────────────

   In LA's reference, colour appears only on the counts, and each hue means something exact — blue
   the set, green accepted, red rejected. Ours has one count and no accepted/rejected axis, so
   painting it would be decoration, and it stays monochrome. Blue is reserved for a single
   statement: THIS IS CURRENTLY ON. The active half of List/Cards, and the vocabulary section you
   are presently scrolled into. Nothing else in the row is coloured — not the download arrow, which
   was blue and is now ink, because "leaves with a file" is not a state.

   ── "ON" NEVER RESTS ON COLOUR ALONE ─────────────────────────────────────────────────────────

   Fill, plus weight 600, plus a 2px inset rule under the label. Two of those three survive with no
   colour at all. A toggle whose only on-state is a hue cannot answer "which view am I in" for a
   reader who cannot see the hue, and that question is the entire job of a toggle.

   ── THE SIX KINDS ────────────────────────────────────────────────────────────────────────────

     1 FACT       `739`                    inert fill, no border, not focusable, no hover
     2 ACTION     dice, shuffle            outline, icon optically centred, monochrome
     3 TOGGLE     List / Cards             one container, one member ON
     4 NAVIGATE   Archetype 11 …           outline; ON when that section is the one in view
     5 FILTER     an applied facet          blue — it is an applied state, so it is on by definition
     6 DATASET    ↓ 739 pretty websites    offered fill, leading glyph, PREVIEW in a second zone

   Kind 4 was handed to me as "filters", and they are not: Archetype/Purpose/Register/Craft scroll
   to four sections of the page they are already on and narrow nothing. Rather than draw them as a
   filter that filters nothing, they were given the state they DO have — a scroll-spy marks the one
   you are looking at. The on-state is now true, and it earns its place in the grammar instead of
   borrowing one.

   ── ONE MORE SIGNAL, AND IT IS THE TYPEFACE ──────────────────────────────────────────────────

   Controls that print DATA are set in --mono: the count, the dataset headline, the section names
   with their tallies. Controls that change the VIEW are set in --sans: List, Cards. It is the same
   noun/verb split the fills make, said a second way, and it is why `739` and `Cards` read as
   different kinds of thing even in a greyscale screenshot. Tabular numerals come along with the
   mono, which is what keeps a changing count from reflowing the row.

   ── CRAFT ────────────────────────────────────────────────────────────────────────────────────

   · 46px = 44px of touch target plus a hairline, on every control, at every width.
   · `font-variant-numeric: tabular-nums` wherever a number prints, so `739` and `11` hold the same
     box and a changing count never reflows the row.
   · Optical centring: the ↓ glyph sits high in its em box and takes a 1px nudge; it is not trusted
     to its own bounds.
   · One radius: --r-s. There is no second value in this bar.
   · Transitions name their properties — background-color, border-color, color. No `transition: all`
     and nothing animates a shadow.
   · Both themes come free because every value here is a token; the light block near the top of this
     file redefines tokens, never rules.
   ══════════════════════════════════════════════════════════════════════════════════════════════ */

/* 0 · ORDER — the dataset leads. LA: "the download label should be the first on the left."
   It was rendered last by `order`, so the corpus row read `739 · dice · shuffle · List/Cards ·
   sites.json`: the one control that says what the tab IS came after four that say what you can do
   to it. Reading order now: what you GET, then what you can DO, then what is APPLIED. */
.ctl-l { order: 1; }
.ctl-r { order: 2; }
.ctl > .pill-rail { order: 3; }

/* THE SHARED BOX. Every rule below sets fill and ink; not one of them sets height or radius. */
.ctl .hit-count,
.ctl .icon-btn,
.ctl .seg,
.ctl .vc-jump a,
.ctl .dl,
.ctl .pill {
  height: 46px; min-height: 46px; box-sizing: border-box;
  border-radius: var(--r-s);
  transition: background-color .15s, border-color .15s, color .15s;
}

/* 1 · FACT — inert. Filled and borderless: in this bar a border is the promise of a press, so the
   one control you cannot press is the one without an edge. No hover, no pointer. */
.ctl .hit-count {
  display: inline-flex; align-items: center; justify-content: center;
  flex: 0 0 auto; min-width: 46px; padding: 0 var(--s-4);
  /* A RELATIVE TINT, NOT A SURFACE TOKEN, AND THE LIGHT THEME IS WHY. `--surface-2` is #212226 on
     dark against a #121317 page — a clear step — but #f4f4f6 on light against #f5f5f7, which is one
     luminance value apart and invisible. The fact lost its fill, and with no border either it went
     straight back to being the bare number LA objected to in the first place. Mixing from `--text`
     inverts with the theme by construction: a light veil on a dark page, a dark veil on a light one,
     the same visible step in both. Caught by looking at the light phone render, not by any gate —
     both themes passed contrast, because contrast is not the thing that broke. */
  border: none; background: color-mix(in srgb, var(--text) 9%, transparent); color: var(--text-2);
  font-family: var(--mono); font-size: 12.5px; font-variant-numeric: tabular-nums;
  letter-spacing: .01em; white-space: nowrap; cursor: default;
}
.ctl .hit-count:hover { background: var(--surface-2); color: var(--text-2); }

/* 2 · ACTION — available. Transparent at rest, so the row reads as mostly air. */
.ctl .icon-btn {
  width: 46px; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); background: transparent; color: var(--text-2);
}
.ctl .icon-btn:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text); }

/* 3 · TOGGLE — one container, exactly one member on. */
.ctl .seg { border: 1px solid var(--border); background: transparent; overflow: hidden; }
.ctl .seg button {
  padding: 0 var(--s-4); border: 0; background: transparent;
  font-family: var(--sans); font-size: 13px; font-weight: 500; color: var(--text-2);
  transition: background-color .15s, color .15s;
}
.ctl .seg button:hover { background: var(--surface-2); color: var(--text); }
.ctl .seg button.on {
  background: var(--blue-soft); color: var(--blue-bright); font-weight: 600;
  box-shadow: inset 0 -2px 0 0 var(--blue);
}

/* 4 · NAVIGATE — the vocabulary's four section controls, with a real on-state. */
.ctl .vc-jump { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: 0; }
.ctl .vc-jump a {
  display: inline-flex; align-items: center; gap: .7ch; flex: 0 0 auto;
  padding: 0 var(--s-4);
  border: 1px solid var(--border); background: transparent; color: var(--text-2);
  font-family: var(--mono); font-size: 12.5px; letter-spacing: .01em;
  text-decoration: none; white-space: nowrap;
}
.ctl .vc-jump a:hover { background: var(--surface-2); border-color: var(--border-strong); color: var(--text); }
/* SEVEN JUMP LINKS HAVE TO FIT BESIDE THE DOWNLOAD PILL, AND THE BREAKPOINT IS MEASURED.
   The Graph tab gained an Episodes section, which made this rail seven links instead of six, and at
   1024px the row wrapped: the download pill is 380px wide, the rail wanted 599, and the bar has 942
   between its gutters. Two rows in the control bar is the defect check-v5-tabs exists to catch, and
   it caught this one.
   The numbers below were found by measuring down until it fit, not chosen. Padding 16→10 per side
   saves 12px on each of seven links and the gap 8→6 saves another 12 — 96px, taking the rail to
   ~503 against 538 available. Only padding and gap move: the control height stays 46 and the radius
   stays one, so the grammar the rest of the bar is checked against is untouched. Above 1180 the
   roomier values take over; below 721 the rail already becomes a horizontal scroller. */
@media (min-width: 721px) and (max-width: 1179px) {
  .ctl .vc-jump { gap: 6px; }
  .ctl .vc-jump a { padding-inline: 10px; }
}
.ctl .vc-jump a .cb-n { color: var(--text-3); font-variant-numeric: tabular-nums; }
.ctl .vc-jump a:hover .cb-n { color: var(--text-2); }
/* ON — the section you are actually looking at. Fill, weight and an inset rule, same three signals
   as the toggle, because it is the same statement. */
.ctl .vc-jump a[aria-current="true"] {
  background: var(--blue-soft); border-color: var(--blue-border); color: var(--blue-bright);
  font-weight: 600; box-shadow: inset 0 -2px 0 0 var(--blue);
}
.ctl .vc-jump a[aria-current="true"] .cb-n { color: var(--blue-bright); }

/* 5 · FILTER — an applied facet. Already blue, and correctly so: it exists only while it is on. */
.ctl .pill { border-radius: var(--r-s); }
.ctl .pill-v, .ctl .pill-k { font-size: 12px; }

/* 6 · DATASET — the offered thing, and the only control with two zones.
   LA: "for the download button which has two parts, I want the title on the left like the download
   icon followed by '739 pretty websites' then the preview on the right."
   The divider moves with them. It was declared on `.dl-get`, which is now the FIRST of the two —
   left there it would have drawn a rule down the outside edge of the control. */
/* ONE FIXED SLOT, NOT A CONTENT-SIZED CONTROL, AND THAT IS WHAT MAKES THE PLACEHOLDER HONEST.
   A `min-width` did not do it: the three tabs' headlines are 222px, 276px and 337px of content, so
   each tab still sized itself and the skeleton could not know which. A fixed slot gives the control
   the same footprint on every tab AND makes the placeholder exact by construction — there is no
   width to guess, because the box never depended on the text. The divider then lands at the same x
   on all three tabs, which is the toolbar reading LA's reference has. Long headlines ellipsize
   rather than push. */
/* `min(380px, 100%)` was the obvious way to write this and it silently did nothing: `.dl` is a flex
   item of `.dl-bar`, which is itself shrink-to-fit, so the percentage resolved against the box's
   own content width and `min()` always chose the content. A flat width plus the flex item's default
   shrink gives the same intent and actually holds — 380px wherever there is room, less where there
   is not. Measured after the change, not assumed. */
.ctl .dl {
  width: var(--dl-w, 380px); min-width: 0;
  /* Same relative tint as the fact, at half strength: the dataset control is OFFERED, which is one
     step up from the transparent row and one step below the inert fill. `--surface-2` here had the
     same light-theme collapse — a white control on a white bar, distinguishable only by its border. */
  border: 1px solid var(--border); background: color-mix(in srgb, var(--text) 4%, transparent);
  overflow: hidden;
}
.ctl .dl:hover { border-color: var(--border-strong); background: color-mix(in srgb, var(--text) 8%, transparent); }
.ctl .dl-get, .ctl .dl-peek { height: 100%; min-height: 0; }
/* `min-width: 0` on both, or the headline never shrinks and never ellipsises — it just runs past
   the control and gets sliced by `.dl`'s overflow, mid-word, with no ellipsis to say so. A flex
   item's default `min-width: auto` refuses to go below its content, which is the single most
   common reason text overflows a flex row. Found by red-teaming the slot down to 210px: the gate
   passed, because the text was clipped by an ancestor rather than ellipsised by itself. */
.ctl .dl-get { border-left: 0; flex: 1 1 auto; min-width: 0; justify-content: flex-start; gap: .8ch; }
.ctl .dl-file { min-width: 0; }
.ctl .dl-peek { border-left: 1px solid var(--border); margin-left: auto; padding: 0 var(--s-4); }
.ctl .dl-get:hover, .ctl .dl-peek:hover { background: rgba(128,128,128,.10); }
/* THE SECOND ZONE IS A ZONE, NOT A CONTROL INSIDE A CONTROL. `preview` carried its own pill —
   a --r-pill lozenge sitting inside an --r-s box, which is the one radius this bar does not have,
   nested one level down. The divider is what separates the two halves; the word does not also need
   a box around it. */
.ctl .dl-peek-pill {
  padding: 0; border: 0; background: transparent; border-radius: 0;
  color: var(--text-2); font-size: 10.5px; letter-spacing: .09em;
}
.ctl .dl-peek:hover .dl-peek-pill { background: transparent; border-color: transparent; color: var(--text); }
/* The headline is the loudest thing in the control, because it is the only part that says what the
   file IS. The filename and the byte size are this control's tooltip and the first two facts in
   the preview modal — an engineer still gets them; a reviewer is no longer shown a filename in
   place of a sentence. */
.ctl .dl-file {
  color: var(--text); font-family: var(--mono); font-size: 12.5px;
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
  overflow: hidden; text-overflow: ellipsis;
}
/* Monochrome. This glyph was --blue-bright, and "leaves with a file" is a role, not a state —
   under this grammar the only blue in the row means ON. */
.ctl .dl-arrow { color: var(--text-2); font-size: 14px; transform: translateY(-1px); }
.ctl .dl-get:hover .dl-arrow { color: var(--text); transform: translateY(0); }

/* THE PLACEHOLDER HOLDS THE BOX, AND THAT IS ITS ONLY JOB. LA screenshotted the Templates tab and
   reported the download as "disappeared": it had not, the manifest fetch had simply not landed and
   the empty group is hidden while it is empty — so the whole bar was a blank 64px stripe, and on
   Templates that is the only control there is. `.dl` carries the same min-width in both states, so
   the real control fills a box that is already the right size and nothing to its right moves. A
   skeleton that reserves the WRONG geometry is worse than none: it trades one flicker for two. */
.ctl .dl-wait { pointer-events: none; }
.ctl .dl-wait .dl-peek-pill, .ctl .dl-wait .dl-arrow { opacity: .45; }
.dl-ghost {
  display: inline-block; height: 11px; width: 176px; max-width: 100%;
  border-radius: var(--r-pill); background: var(--surface-3);
}
@media (prefers-reduced-motion: no-preference) {
  .dl-ghost { animation: dlGhost 1.4s ease-in-out infinite; }
}
@keyframes dlGhost { 0%, 100% { opacity: .5; } 50% { opacity: .9; } }

/* FOCUS, ON EVERY PRESSABLE THING IN THE ROW, IN ONE PLACE. The fact is absent from this list on
   purpose: it is not in the tab order, and a focus ring on something that cannot be focused is a
   rule nobody ever sees fire. */
.ctl .icon-btn:focus-visible,
.ctl .seg button:focus-visible,
.ctl .vc-jump a:focus-visible,
.ctl .dl-get:focus-visible,
.ctl .dl-peek:focus-visible,
.ctl .pill-x:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* THE FOUR SECTION CONTROLS SCROLL RATHER THAN WRAP ON A PHONE. At 390 they wrapped to two rows and
   the vocabulary bar became 170px of chrome above the content — three rows where the corpus has
   two. A horizontal rail is the pattern this site already uses for the facet pills, so the bar
   keeps one row per group on every tab and the three tabs stay the same height. */
@media (max-width: 720px) {
  .ctl .vc-jump {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
    /* NO `padding-bottom: 2px`. It was a gutter for a scrollbar that the line above it hides, so it
       reserved 2px for something that never renders — and because this rail is the wrapped second
       row of the vocabulary bar, those 2px pushed every control in that row up and left the band
       measurably bottom-heavy on a phone after the 20px margin had already been found and removed.
       Two pixels is exactly the size of thing that survives a fix, and the last 3px of a 19px skew
       was this. */
    max-width: 100%;
  }
  .ctl .vc-jump::-webkit-scrollbar { display: none; }
}

/* THE FIXED SLOT IS A DESKTOP IDEA AND IT HAS TO LET GO ON A PHONE. `width: 380px` overflowed every
   tab below 400px — 80px at 320 — because `.dl` sits inside two shrink-to-fit flex boxes, so
   neither `max-width: 100%` nor the item's own shrink had a definite width to resolve against.
   Give the chain a definite one: below 720 the group is a full row of the bar, and the control
   fills it. Caught by check-v5-tabs at four widths on all three tabs, which is exactly the class of
   defect that gate exists for — a change that looks right at 1440 and drags the page sideways at
   375. */
@media (max-width: 720px) {
  .ctl-l { flex: 1 1 100%; min-width: 0; max-width: 100%; }
  .ctl .dl-bar { flex: 1 1 100%; min-width: 0; max-width: 100%; }
  .ctl .dl { width: 100%; max-width: 100%; }
}

/* THE SLOT GIVES UP ITS COMFORT BEFORE IT GIVES UP ITS SENTENCE. Below 480 the control has ~280px
   to hold a leading glyph, a headline and a second zone, and at 320 the vocabulary headline was
   being ellipsised — measured by check-v5-tabs, not noticed by eye. Padding and type step down so
   the sentence survives; the 46px box and the single radius do not move, because those are the
   grammar and the padding is not. */
@media (max-width: 480px) {
  .ctl .dl-get { padding-left: var(--s-3); padding-right: var(--s-2); gap: .6ch; }
  .ctl .dl-peek { padding: 0 var(--s-3); }
  .ctl .dl-file { font-size: 11.5px; }
  .ctl .dl-peek-pill { font-size: 9.5px; letter-spacing: .06em; }
}

/* The retired-tab line's styles were removed with it. Two orphaned declaration tails were left
   behind here — the selectors and opening braces went, their closing braces did not — and two
   stray closing braces at the top of an appended stylesheet close blocks that were never opened,
   so every rule after them is discarded. That is why the rule hiding .tab-short stopped applying
   and all three tabs read "Golden CorpusCorpus" for a whole deploy: the rule was in the file,
   correct, and unreachable. A regex that deletes lines starting with a selector does not delete
   the rule; a multi-line rule leaves its tail. */

/* v4 carried eleven tabs, so its strip had to scroll on a phone and the trailing edge was masked
   to say so. v5 has four, and four can FIT — which is better than four that scroll, because a tab
   the reader must discover by swiping is a tab most readers never see, and Templates is the one
   Mason's team actually came for. Tightened only below 480px; the desktop strip is untouched. */
/* 620px, not 480px. The first attempt broke at 480 and the strip scrolled again from 481 to ~560,
   where the long labels are back but still do not fit — a breakpoint chosen by guess rather than
   by the width at which the content actually stops fitting. Measured, not assumed. */
/* THESE TWO FONT-SIZES SET THE STRIP'S HEIGHT, AND SOMETHING ELSE OFFSETS ITSELF AGAINST IT.
   The control band is pinned directly under the tab strip at top: var(--tabs-h), declared in the
   THE PINNED CHROME block at the top of site/src/styles.css as 51 / 48 / 46px at exactly the
   620 and 360 breakpoints below. Change a font-size or a padding here and the strip's height moves
   with it — move --tabs-h in the same commit. check-v5-tabs.mjs measures both and fails if they
   disagree, so this is a reminder, not the guard.
   (NO BACKTICKS ANYWHERE IN THIS STRING: it is a JS template literal, and one backtick in a comment
   ends it mid-stylesheet. That is a defect this project has already shipped more than once.) */
/* ONE LEFT EDGE. LA: "make sure left is always aligned — the title and nav." Every tab carries
   --s-4 of its own padding, so the strip's first word sat 12px right of the content it heads on
   every tab. The strip's left pad is the content's pad less the tab's own, so the first tab's
   glyphs land on the same x as the page's h1 and rules. /method overrides this against its own
   wider measure in its own sheet. */
.tabs-in{padding-left:calc(clamp(20px,4vw,48px) - var(--s-4))}
.tabs-in .tab:first-child{margin-left:0}
.tabs-in .tab-short{display:none}
@media (max-width: 620px){
  .tabs-in{gap:0;padding-left:calc(clamp(20px,4vw,48px) - 10px)}
  .tabs-in .tab{padding-left:10px;padding-right:10px;font-size:13px;letter-spacing:-.01em;
    white-space:nowrap}
  /* Swap the label, not the font size. Setting font-size:0 on the anchor and restoring it on a
     ::after left the zero-height line box in place, and "Corpus" rendered on its own baseline
     above the other three. Two spans, one shown at a time, keep every tab in the same line box. */
  .tabs-in .tab-long{display:none}
  .tabs-in .tab-short{display:inline}
}
/* 320px is the narrowest viewport the project tests, and it is the one that decides these numbers:
   at 7px padding / 12.5px the strip still overflowed by a few pixels and scrolled. Measured down
   until it fit rather than picked. Above 360 the roomier values take over. */
@media (max-width: 360px){
  .tabs-in .tab{padding-left:5px;padding-right:5px;font-size:12px;letter-spacing:-.02em}
}

/* NOTHING IN THIS APPENDED STRING PINS ANYTHING, AND THAT IS THE POINT.

   This tail used to end with .topbar{position:sticky;top:0;z-index:40} under a comment reading
   "Only the bar sticks, not the tab strip… the tab strip is reachable by going back up". Both
   halves were false by the time anyone read them: styles.css had ALREADY pinned .tabs at top: 0
   with the same z-index, and again in a second rule inside @media (max-width: 760px), and .bar
   in a third. Four rules, two files, three of them commented as the only sticky element on the
   page — so all three bands landed on y=0 and nav.bar, last in DOM order, painted over the tab
   strip. Measured at scrollY 3000: elementFromPoint over the active tab returned span.dl-file.
   Every tab link, both wordmark links and the theme control were focusable and covered (WCAG 2.2
   SC 2.4.11), and the corpus tab is 280,611px tall at 375 — LA's "can't go back once I am there",
   caused by the rule written to prevent it.

   A generated stylesheet tail is the worst possible place to keep a layout decision: it is
   invisible to anyone reading styles.css, which is where a reader looks. So the decision moved,
   whole, into the THE PINNED CHROME block at the top of site/src/styles.css — .tabs pinned at
   top: 0, .bar pinned beneath it at top: var(--tabs-h), .topbar explicitly position: static.
   The topbar scrolls away now, deliberately: it carries a wordmark and a theme toggle, and pinning
   it costs 115px at 1440 and 229px of a 667px phone for two things that are not navigation.

   IF YOU NEED A BAND PINNED, EDIT THAT BLOCK. Do not add a rule here. */
