/* ─────────────────────────────────────────────────────────────────────────────
   Alterspective base tokens.

   Copied verbatim from the Design System Showcase house skin
   (Alterspective-Design-System-Showcase/examples/flow-app.html) so this app
   renders the same house as the rest of the estate. Values are NOT minted here
   — a new token value is a Foundry FR, never a local invention
   (adl-for-ai.md §5). adl.css (public/adl.css) carries the card anatomy and is
   linked BEFORE this file, as its load-order contract requires.

   Theme: light on bare :root, dark under BOTH prefers-color-scheme and an
   explicit data-theme, so the system default and a deliberate choice agree.
   Focus ring is the teal --flow token; citrus stays the lean seal only
   (ADL-DEC-038).
   ────────────────────────────────────────────────────────────────────────── */

/* ─────────────────────────────────────────────────────────────────────────────
   THE BRAND TYPEFACES — first-party, self-hosted, and until now absent.

   The three families below were NAMED in --as-font-display / --as-font-body /
   --as-font-mono below and NOTHING LOADED THEM. There was no @font-face rule
   anywhere in this app: verified on the rendered page, the only @font-face
   rules the document carried belonged to Next.js's development overlay. The
   families resolved on developer machines because those machines have them
   installed in Windows, so the board looked correct to everyone who could have
   noticed. Every other viewer silently fell through to Georgia, Arial and
   Consolas, and nothing anywhere reported it — no console warning, no failed
   request, no visual error. That silence is what made this survive review.

   A brand typeface MUST be delivered by an @font-face the surface itself
   declares, never inherited from whatever happens to be present (UX-BRAND-07a).
   These rules are that declaration.

   The files are generated by `node scripts/build-fonts.mjs`, which converts the
   licensed .otf/.ttf sources to woff2 (measured 70.0% smaller overall: 1,585,580
   bytes in, 476,420 out) and writes public/fonts/fonts.json with the SHA-256 of
   every shipped file and of every source it came from. `--verify` re-checks the
   shipped bytes against that manifest. The .otf/.ttf sources are deliberately
   NOT committed: they stay canonical in the brand-guide repository, and woff2
   is the delivery format only (UX-BRAND-07a). Provenance, licences and the
   brand-guide gaps found while doing this: docs/brand-font-provenance.md.

   WHICH CUTS, AND WHY ONLY THESE

   Chronicle Display is licensed to Alterspective at Light 300 / Roman 400 /
   Semibold 600. Semibold is the cut that paints — `.display` is weight 600 —
   and Roman 400 ships as the family's own default so a lighter --as-font-display
   is a real face rather than a synthesised one. Black is NOT a licensed weight
   and is not shipped even though the source folder carries it. There is no
   upright Light 300 in the brand guide at all (only Light *Italic*), so the
   licensed 300 cannot be shipped from here — a brand-guide gap, recorded, not
   worked around.

   Montserrat ships 400/500/600/700 upright and 400 italic. That is measured
   against real usage, not guessed: this file declares 600, 650, 680 and 700,
   and CSS font matching resolves a desired weight above 500 upward to the next
   available face, so 650 and 680 both paint as 700 and there is no 650/680 cut
   to make. The italic is genuinely used — app/page.tsx renders an <em> — and
   without the real cut a browser slants the upright, which is not the typeface.
   No other italic is used, so none ships. 500 is the one cut nothing exercises
   today; it is here because an unused @font-face costs zero bytes on the wire
   (a face is fetched only when something needs it to paint) and because a
   future weight:500 silently rendering as 400 is precisely the invisible
   failure this change exists to end.

   JetBrains Mono ships Regular 400 only — `.mono` and `.evidence-frame` set no
   weight and no bold mono is used anywhere. Its woff2 is the vendor's OWN build
   from the v2.304 release archive, copied verbatim rather than re-compressed
   from their .ttf: there is nothing for our toolchain to get wrong, and the
   archive is pinned by tag and by SHA-256.

   font-display: swap on every face, deliberately. The fallback stacks in
   --as-font-* stay exactly as they were: Georgia / Arial / Consolas are the
   technical loading fallback (UX-BRAND-07 § Font Licensing), and swap is what
   makes them do that job — text is readable immediately and is repainted in the
   brand face when it arrives, instead of a blocking invisible-text period.

   There is no <link rel="preload"> for any of these. app/layout.tsx belongs to
   another workstream and preload is a judgement about first paint, not a
   correctness fix; the fonts load correctly without it.
   ────────────────────────────────────────────────────────────────────────── */

@font-face {
  font-family: 'Chronicle Display';
  src: url('/fonts/chronicle-display-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Chronicle Display';
  src: url('/fonts/chronicle-display-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Montserrat';
  src: url('/fonts/montserrat-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Montserrat';
  src: url('/fonts/montserrat-400-italic.woff2') format('woff2');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: 'Montserrat';
  src: url('/fonts/montserrat-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Montserrat';
  src: url('/fonts/montserrat-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Montserrat';
  src: url('/fonts/montserrat-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('/fonts/jetbrains-mono-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --as-marine: #075156;
  --as-citrus: #ABDD65;
  --as-navy: #17232D;
  --as-font-display: 'Chronicle Display', Georgia, 'Times New Roman', serif;
  --as-font-body: 'Montserrat', 'Helvetica Neue', Arial, sans-serif;
  --as-font-mono: 'JetBrains Mono', Consolas, 'Courier New', monospace;
  --paper: #F2F7F7;
  --surface: #FFFFFF;
  --raise: #FFFFFF;
  --sunk: #E5EEEF;
  --edge: #CCD2D6;
  --ink: #17232D;
  --ink-2: #3A434D;
  --ink-3: #4D5A66;
  --ink-4: #667682;
  --rule: #CCD2D6;
  --rule-2: #A6B5BD;
  --flow: #075156;
  --flow-2: #075156;
  --flow-hover: #008080;
  --flow-soft: #E5EEEF;
  --flow-ink: #075156;
  --focus: #075156;
  --lean: #ABDD65;
  --ok: #2C8248;
  --warn: #A85F00;
  --danger: #BA1A1A;
  --info: #075156;
  --ok-soft: #DDF0E2;
  --warn-soft: #F6E8D2;
  --danger-soft: #F8E0DE;
  --info-soft: #E5EEEF;
  --ok-ink: #1A5E2A;
  --warn-ink: #7A4400;
  --code-bg: #EDF3F3;

  /* ROLE ALIAS — NOT a token, and NOT a minted value.
     It holds no colour of its own; it selects between two EXISTING house
     tokens per theme, and the token values above are untouched.

     Why it exists: the quiet tier --ink-3 carries every micro-label, .fine,
     .sub and .meta line. In LIGHT it is correct and passes AA on every surface
     (6.0–7.07:1). In DARK its value #99A4AC lands on --raise/--sunk (#3A434D)
     at 3.95:1, and all of these are small text, so AA wants 4.5:1. --ink-2
     clears it at 6.58:1.

     The choice, made deliberately: a flat move to --ink-2 in BOTH themes would
     fix dark but push light micro-labels from 6.54:1 to 9.29:1 — louder than
     the tier is meant to be, and the quietness of that tier is the design
     intent (ADL F1, signal earns attention). Aliasing per theme keeps light
     exactly as designed and fixes dark only. It is one line per theme block
     rather than a duplicated selector list in two dark blocks, so it does not
     fragment the file.

     This is a workaround for a house-palette gap, not a fix for it: --ink-3
     cannot express "muted small text on a raised dark surface" at AA. That is
     a Foundry FR. When the token set answers it, delete this alias and put
     --ink-3 back. */
  --muted-ink: var(--ink-3);

  /* ROLE ALIAS — same mechanism as --muted-ink above, second use, same rules:
     no value of its own, selects between EXISTING tokens per theme, mints
     nothing.

     --danger painted on --danger-soft is 5.14:1 in light (passes) and 4.22:1
     in dark (fails 4.5:1), because in dark the pair collapses to #E8756C on
     #3D3238. The token that would fix it is --danger-ink, and it DOES NOT
     EXIST (Foundry, Design-System-Showcase #29). This alias is deliberately
     NOT called --danger-ink: that name belongs to the token #29 will add, and
     using it here would read as having minted it.

     Light keeps --danger, because light already passes AND red-on-pale-red is
     the correct rendering of the role — changing it would be a semantic loss
     with no accessibility gain. Dark takes --ink-2 (8.06:1). That is not a
     compromise, it is what mature dark themes do: a saturated red cannot
     carry both its hue and AA contrast against a dark red tint, so the text
     goes light-neutral and the ROLE is carried by the tint, the border, the
     glyph and the words — none of which this change touches, so UX-AX-05 is
     satisfied without the text colour having to carry it alone. */
  --danger-fg: var(--danger);

  --sh-1: 0 1px 2px rgba(23, 35, 45, .08);
  --sh-2: 0 2px 6px rgba(23, 35, 45, .10), 0 14px 30px -16px rgba(23, 35, 45, .28);
  --sh-pop: 0 4px 10px rgba(23, 35, 45, .14), 0 18px 40px -18px rgba(23, 35, 45, .35);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #17232D;
    --surface: #2A323A;
    --raise: #3A434D;
    --sunk: #3A434D;
    --edge: #3A434D;
    --ink: #F5F6F7;
    --ink-2: #CCD2D6;
    --ink-3: #99A4AC;
    --ink-4: #667682;
    --rule: #3A434D;
    --rule-2: #4D5A66;
    --flow: #4DB8B8;
    --flow-2: #4DB8B8;
    --flow-hover: #80CCCC;
    --flow-soft: color-mix(in srgb, #4DB8B8 20%, #17232D);
    --flow-ink: #B3E0E0;
    --focus: #4DB8B8;
    --lean: #ABDD65;
    --ok: #77C38F;
    --warn: #E2A53F;
    --danger: #E8756C;
    --info: #4DB8B8;
    --ok-soft: color-mix(in srgb, #77C38F 18%, #17232D);
    --warn-soft: color-mix(in srgb, #E2A53F 18%, #17232D);
    --danger-soft: color-mix(in srgb, #E8756C 18%, #17232D);
    --info-soft: color-mix(in srgb, #4DB8B8 18%, #17232D);
    --ok-ink: #9ED4AF;
    --warn-ink: #EDB65C;
    --code-bg: #232B33;
    /* See the alias notes on :root. --ink-3 is 3.95:1 on --raise/--sunk here,
       and --danger is 4.22:1 on --danger-soft (#E8756C on #3D3238). */
    --muted-ink: var(--ink-2);
    --danger-fg: var(--ink-2);
    --sh-1: none;
    --sh-2: none;
    --sh-pop: 0 6px 14px rgba(0, 0, 0, .5), 0 20px 44px -18px rgba(0, 0, 0, .7);
  }
}

:root[data-theme="dark"] {
  --paper: #17232D;
  --surface: #2A323A;
  --raise: #3A434D;
  --sunk: #3A434D;
  --edge: #3A434D;
  --ink: #F5F6F7;
  --ink-2: #CCD2D6;
  --ink-3: #99A4AC;
  --ink-4: #667682;
  --rule: #3A434D;
  --rule-2: #4D5A66;
  --flow: #4DB8B8;
  --flow-2: #4DB8B8;
  --flow-hover: #80CCCC;
  --flow-soft: color-mix(in srgb, #4DB8B8 20%, #17232D);
  --flow-ink: #B3E0E0;
  --focus: #4DB8B8;
  --lean: #ABDD65;
  --ok: #77C38F;
  --warn: #E2A53F;
  --danger: #E8756C;
  --info: #4DB8B8;
  --ok-soft: color-mix(in srgb, #77C38F 18%, #17232D);
  --warn-soft: color-mix(in srgb, #E2A53F 18%, #17232D);
  --danger-soft: color-mix(in srgb, #E8756C 18%, #17232D);
  --info-soft: color-mix(in srgb, #4DB8B8 18%, #17232D);
  --ok-ink: #9ED4AF;
  --warn-ink: #EDB65C;
  --code-bg: #232B33;
  /* See the alias notes on :root. --ink-3 is 3.95:1 on --raise/--sunk here,
     and --danger is 4.22:1 on --danger-soft (#E8756C on #3D3238). */
  --muted-ink: var(--ink-2);
  --danger-fg: var(--ink-2);
  --sh-1: none;
  --sh-2: none;
  --sh-pop: 0 6px 14px rgba(0, 0, 0, .5), 0 20px 44px -18px rgba(0, 0, 0, .7);
}

/* ── page ──────────────────────────────────────────────────────────────── */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--as-font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--flow-2); text-underline-offset: 2px; }
a:hover { color: var(--flow-hover); }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}

.wrap { max-width: 1180px; margin: 0 auto; padding: 0 26px 90px; }
.wrap.narrow { max-width: 640px; padding-top: 12vh; }

/* The page's one Chronicle element, and the floor is the rule, not a taste
   call. Chronicle Display MUST be used at 36px and above; below 36px the
   typeface is Montserrat (UX-BRAND-07). The single carve-out for sub-36px
   Chronicle is a CLOSED set — `blockquote.pull-quote`, `.intro`, `.lede`, or
   role="doc-introduction", at weight 300 or 400, in [24px, 36px) — and an
   `h1.display` at weight 600 is not in it and cannot be argued into it
   (UX-BRAND-07b).

   The floor was 1.8rem. `clamp()` minima ARE the rule's evidence, not a
   refinement of it (UX-BRAND-07 enforcement note), and this one computed to
   28.8px at a 320px viewport — 4vw is 12.8px there, so every narrow view of
   this app was painting Chronicle 7.2px below its floor, invisible to any
   check run at 1440px. Raised to 2.25rem, which is 36px against a 16px ROOT.

   The root, precisely. `rem` resolves against `html`, and nothing in this file
   or in adl.css sets `html { font-size }`, so that 16px comes from the user
   agent — confirmed by reading the computed root font-size, not assumed. The
   `body { font-size: 16px }` rule further down has no bearing on it, and an
   earlier version of this note credited it, which would have sent the next
   reader to the wrong line. If anyone ever sets a percentage root (`html {
   font-size: 87.5% }` is a common way to get a 14px base) this floor silently
   becomes 31.5px and drops back under the rule while `body` still reads 16px
   and looks untouched. Writing the floor as `36px` would be immune to that; it
   stays in `rem` so the heading still scales with a reader's own font-size
   preference, which is worth more than the immunity.

   Chosen over the other permitted fix — swapping `.display` to Montserrat
   under a breakpoint — because this element is the page's h1 in every surface
   it appears on, and a heading that changes typeface family at a width is a
   visible break in the brand voice for a problem that one number solves. It
   also keeps the family constant, so the used-font probe has one answer at
   every breakpoint instead of two.

   Honest consequence: with a 2.25rem floor and a 2.4rem ceiling, 4vw only
   selects between them from 900px to 960px, so this is now a near-fixed
   36-38.4px rather than a fluid 28.8-38.4px. That is the point — the fluid
   range was fluid THROUGH the floor. Measured after the change: 36px at 320px,
   38.4px at 1440px, Chronicle Display at both.

   `overflow-wrap: anywhere` guards the larger floor against the 320px reflow
   fix on `.board`: a lab record's name comes from another team's lab.yaml and
   may be one long unbroken token, which at 36px is wider than a 268px content
   box and would push the document into a horizontal scroll (UX-AX-14). It only
   engages where a word genuinely cannot fit. */
.display {
  font-family: var(--as-font-display);
  font-size: clamp(2.25rem, 4vw, 2.4rem);
  line-height: 1.08;
  font-weight: 600;
  margin: 0;
  text-wrap: balance;
  overflow-wrap: anywhere;
}

.lede { color: var(--ink-2); margin: .5rem 0 0; max-width: 62ch; }

/* The micro-label. One definition, so a field caption looks the same wherever
   it appears — tile, card, detail row, or a one-off section. */
.lb {
  display: block;
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted-ink);
  font-weight: 680;
}

.fine { color: var(--muted-ink); font-size: .82rem; }
.mono { font-family: var(--as-font-mono); font-size: .85rem; }
.pad { padding: 1.3rem 1.45rem; }

/* ── masthead ──────────────────────────────────────────────────────────── */

.masthead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding: 2.4rem 0 1.4rem;
}

.masthead .who { font-size: .8rem; color: var(--muted-ink); text-align: right; }

/* Liveness stamp. Declared mode is `current`: this is a freshness receipt,
   not a control — there is no Reload button (ADL-LIVE-01). */
.asof {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  font-size: .76rem;
  color: var(--muted-ink);
  font-weight: 600;
}

.asof .dot {
  width: .5rem;
  height: .5rem;
  border-radius: 999px;
  background: var(--ok);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent);
}

.asof[data-state="checking"] .dot { background: var(--warn); box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 22%, transparent); }
.asof[data-state="failing"] .dot { background: var(--danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 22%, transparent); }

/* ── rollup tiles ──────────────────────────────────────────────────────── */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: .85rem;
  margin: 0 0 1.6rem;
}

.tile {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: .85rem 1rem;
  display: block;
}

.tile .lb {
  display: block;
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted-ink);
  font-weight: 680;
}

.tile .n {
  display: block;
  font-size: 1.9rem;
  line-height: 1.1;
  font-weight: 680;
  letter-spacing: -.02em;
  margin-top: .15rem;
}

.tile .sub { display: block; font-size: .76rem; color: var(--muted-ink); }
.tile.alert { border-color: color-mix(in srgb, var(--warn) 55%, transparent); background: var(--warn-soft); }
.tile.alert .n { color: var(--warn-ink); }
.tile.calm .n { color: var(--ok-ink); }

/* An alert tile repaints its background but its caption and sub-line kept the
   page's --ink-3, which is mid-grey. On the dark theme that lands --ink-3
   (#99A4AC) on --warn-soft and fails AA (UX-AX-05). --warn-ink is the token
   that already exists for "foreground on a warn surface", in both themes. */
.tile.alert .lb,
.tile.alert .sub { color: var(--warn-ink); }

/* ── filters ───────────────────────────────────────────────────────────── */

.filters { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin: 0 0 1.1rem; }

/* The 44px target-size floor lives HERE, on the base class, and not on an
   opt-in modifier — that placement is the fix, not a detail of it.

   Every `.chip` in this app is an interactive control: the status filters, the
   Admin Settings link, the sign-in link, Add Repository, Download Artifact.
   There is no decorative chip for the floor to be wrong about. When the floor
   was a separate `.chip.tap` modifier, two call sites remembered it and three
   did not, and the three that forgot were the board's five status filters
   (measured 29px), the Admin Settings link, and the evidence download link —
   the most-used control in the app among them (UX-AX-04, 44x44 shipping
   minimum). A rule every call site must opt into is a rule that will be missed
   again by the next chip somebody adds. Carried on the base class it cannot be.

   inline-flex + center is what makes the min-height actually show as height on
   an <a>, which is otherwise inline and ignores it. */
.chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  font: inherit;
  font-size: .8rem;
  font-weight: 650;
  padding: .3em .85em;
  border-radius: 999px;
  border: 1px solid var(--rule-2);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}

.chip[aria-pressed="true"] { background: var(--flow-soft); border-color: var(--flow-2); color: var(--flow-ink); }
.chip:hover { border-color: var(--flow-2); }

.search {
  font: inherit;
  font-size: .85rem;
  padding: .38em .8em;
  border-radius: 6px;
  border: 1px solid var(--rule-2);
  background: var(--surface);
  color: var(--ink);
  min-width: 220px;
  margin-left: auto;
}

/* The placeholder was the one piece of text on these surfaces that no rule in
   this file painted, so it fell through to the browser's own default — a fixed
   mid-grey that knows nothing about the theme behind it. Measured, that default
   is rgb(117,117,117): 4.61:1 on the light --surface (a marginal pass) and
   2.82:1 on the dark one, against the 4.5:1 small-text requirement (UX-AX-05).
   UX-BRAND-08 names this failure directly — dark mode must not rely on
   inherited or default colours.

   --muted-ink is the existing alias for "quiet small text that must clear AA in
   BOTH themes", already carrying every .fine, .sub and micro-label in this
   file. Nothing is minted here; the placeholder simply joins the tier it always
   belonged to. Measured after the change, on the --surface this input actually
   sits on: 7.07:1 light, 8.51:1 dark.

   `opacity: 1` is load-bearing, not tidying. Firefox applies its own default
   opacity to ::placeholder, which would composite the colour back toward the
   field and quietly undo the ratios above. */
.search::placeholder { color: var(--muted-ink); opacity: 1; }

/* ── lab cards ─────────────────────────────────────────────────────────── */

/* `min(340px, 100%)`, not a bare `340px`, is what keeps this off a phone's
   horizontal scrollbar. A bare minimum is a floor the track cannot go below, so
   under a 340px container the single column stayed 340px wide and pushed the
   document past the viewport: measured at a 320px viewport, scrollWidth 366
   against clientWidth 305, with `article.adl-card.lab` as the first element
   crossing the edge. Content must reflow at 320px without horizontal scrolling
   (UX-AX-14), and the same guard covers 200% zoom, which reaches the same
   narrow container by the other route.

   Above 340px the two forms are identical — `min()` yields 340px and the layout
   is unchanged — so this costs nothing at any width that was already working. */
.board { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr)); gap: 1rem; }

.lab { display: flex; flex-direction: column; gap: .55rem; padding: 1.05rem 1.15rem 1.1rem; }
.lab .top { display: flex; align-items: flex-start; gap: .6rem; }

.lab .name {
  font-size: 1.02rem;
  font-weight: 680;
  letter-spacing: -.01em;
  color: var(--ink);
  text-decoration: none;
  flex: 1;
}

.lab .name:hover { color: var(--flow-hover); text-decoration: underline; }
.lab .q { color: var(--ink-2); font-size: .92rem; margin: 0; }
.lab .meta { display: flex; gap: .9rem; flex-wrap: wrap; font-size: .76rem; color: var(--muted-ink); margin-top: auto; padding-top: .3rem; }
.lab .meta b { font-weight: 650; color: var(--ink-2); }

.pill.p-mute { background: var(--sunk); color: var(--muted-ink); }

/* Status pills — page-local foreground correction, using existing tokens only.
 *
 * adl.css paints p-ok as --ok on --ok-soft and p-warn as --warn on
 * --warn-soft. Both are a solid colour used as text on its own tint, and in
 * LIGHT both land just under AA: measured 4.01:1 and 4.04:1 against the 4.5:1
 * small-text requirement (UX-AX-05).
 *
 * The "-ink" tokens ARE the foreground-on-soft-background role, and this file
 * already uses them that way for .tile.alert and .attn. Swapping only the
 * foreground keeps the tint, the shape and the semantics identical, and clears
 * AA in both themes: --ok-ink 6.58 light / 6.60 dark, --warn-ink 6.55 / 6.20.
 *
 * This overrides adl.css rather than editing it. That is the sanctioned
 * extension point, not a hack: adl.css is vendored verbatim and app.css is
 * linked AFTER it in app/layout.tsx precisely so page styles can correct it.
 * Same specificity (0,2,0), later file wins.
 *
 * p-info is left alone: --flow-ink on --info-soft already measures 7.67 light
 * / 7.98 dark. p-dan takes --danger-fg — see the Foundry note below.
 */
.pill.p-ok { color: var(--ok-ink); }
.pill.p-warn { color: var(--warn-ink); }
.pill.p-dan { color: var(--danger-fg); }

/* ─────────────────────────────────────────────────────────────────────────
 * FOUNDRY GAP — Design-System-Showcase #29: --danger-ink is missing.
 *
 * ONE token pair is behind every one of these: --danger painted as text on
 * --danger-soft. Measured 5.14:1 in light (passes) and 4.20–4.22:1 in dark
 * (fails), against the 4.5:1 small-text requirement of WCAG 2.2 AA
 * (UX-AX-05). In dark the pair collapses to #E8756C on #3D3238.
 *
 * The five selectors that painted it, all dark-theme failures, all now
 * routed through the --danger-fg alias:
 *
 *   .pill.p-dan          the Blocked / Declined status pill      (adl.css)
 *   .attn.t-dan          the danger attention line               (this file)
 *   .problems li         an unreadable record row                (this file)
 *   .problems li .mono   the file path inside that row           (inherited)
 *   .problems li a.mono  the link to it on GitHub                (inherited)
 *
 * Two of these are invisible to a scan of a healthy board: .problems renders
 * zero rows once records are readable, and no lab is currently blocked. They
 * were found by computing the pair, not by scanning a page — which is the
 * only method that covers a colour pair whose element may not be rendered.
 *
 * NOT changed, deliberately: .icon-btn keeps --danger on its --danger-soft
 * hover. Its glyph is 20px/700, which is large text, so the requirement is
 * 3:1 and 4.22:1 clears it — and it is a destructive control where the red
 * is worth keeping. .adl-badge.b-danger carries the same pair in adl.css but
 * is not used by this app; it needs no local override and is left for #29.
 *
 * The real fix is #29 adding --danger-ink (and --info-ink, absent for the
 * same reason). When it lands, delete --danger-fg and point these three
 * rules at the real token. Do not invent a value here in the meantime.
 *
 * ── RE-CHECKED at the adl.css re-sync to upstream 768e32b (2026-08-11) ──
 *
 * Upstream #29 moved HALF of the distance and it is the wrong half to act on.
 * adl.css now READS the tokens — `.pill.p-dan` is `var(--danger-ink,
 * var(--danger))` and `.pill.p-info` is `var(--info-ink, var(--flow-ink,
 * var(--info)))` — but NOTHING DEFINES THEM. Not in upstream adl.css, not in
 * this file, nowhere in this repository. Both `var()` calls therefore take
 * their fallback and land on exactly the colours they landed on before: p-dan
 * on --danger, p-info on --flow-ink.
 *
 * A consumed-but-undefined custom property is the trap this comment exists to
 * disarm. It reads at a glance as "the token landed, the workaround can go",
 * and deleting --danger-fg on that reading would return the dark theme to
 * --danger on --danger-soft — the 4.20:1 failure above, re-shipped, with the
 * comment that explained it removed at the same time.
 *
 * MEASURED after the re-sync, in a browser, from getComputedStyle:
 *   .pill.p-dan   light  #BA1A1A on #F8E0DE  5.14:1  pass
 *                 dark   #CCD2D6 on #3D3238  8.06:1  pass  (via --danger-fg)
 *   .pill.p-info  light  #075156 on #E5EEEF  7.67:1  pass
 *                 dark   #B3E0E0 on #213E46  7.98:1  pass
 * With --danger-fg removed, dark p-dan measures #E8756C on #3D3238 = 4.22:1
 * — a fail. That counterfactual was run, not reasoned about.
 *
 * DISPOSITION: --danger-fg STAYS. Re-open this only when `--danger-ink`
 * resolves to a value, which is a one-line check, not a reading of the CSS:
 *   getComputedStyle(document.documentElement).getPropertyValue('--danger-ink')
 * Empty string means it still does not exist.
 * ───────────────────────────────────────────────────────────────────────── */

/* The attention line is the only place the board raises its voice. It always
   states the action, never merely the condition (F5). */
.attn {
  display: flex;
  gap: .5rem;
  align-items: flex-start;
  font-size: .82rem;
  font-weight: 600;
  border-radius: 6px;
  padding: .5rem .7rem;
  background: var(--warn-soft);
  color: var(--warn-ink);
  border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
}

/* Same defect as .tile.alert: a micro-label inside an attention band must take
   the band's own foreground, not the page's --ink-3. `inherit` is deliberate —
   it stays correct for all three tones below without repeating the mapping. */
.attn .lb { color: inherit; }

.attn.t-dan { background: var(--danger-soft); color: var(--danger-fg); border-color: color-mix(in srgb, var(--danger) 30%, transparent); }

/* The receipt tone. Success/receipt role tokens only — --lean stays the lean
   seal and is never borrowed as a general green (ADL-DEC-038). */
.attn.t-ok {
  background: var(--ok-soft);
  color: var(--ok-ink);
  border-color: color-mix(in srgb, var(--ok) 30%, transparent);
  border-left: 4px solid var(--ok);
}

.next {
  font-size: .82rem;
  color: var(--ink-2);
  border-left: 3px solid var(--lean);
  padding-left: .6rem;
}

.next .lb { display: block; font-size: .6rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted-ink); font-weight: 680; }

/* ── sections, detail, misc ────────────────────────────────────────────── */

h2.sec { font-size: 1.05rem; font-weight: 680; letter-spacing: -.01em; margin: 2.2rem 0 .8rem; }

.empty { color: var(--muted-ink); background: var(--sunk); border-radius: 8px; padding: 1.4rem; text-align: center; }

.problems { border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent); border-radius: 8px; background: var(--danger-soft); padding: .9rem 1.1rem; }
.problems li { color: var(--danger-fg); font-size: .84rem; margin: .3rem 0; }
/* `inherit` is what carries --danger-fg onto the links and mono spans inside a
   problem row, so they are fixed by the rule above rather than needing their
   own. Do not change this to an explicit colour. */
.problems .mono { color: inherit; }

.kv { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem 1.4rem; margin: 1.2rem 0; }
.kv .lb { display: block; font-size: .6rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted-ink); font-weight: 680; }
.kv .vl { display: block; font-size: .95rem; }

.reason { display: block; margin: 1rem 0; }
.reason .lb { display: block; font-size: .6rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted-ink); font-weight: 680; }

/* The one primary action. Background is --flow (Marine), the brand/primary
   role; the foreground is --on-flow where a brand scope defines it, falling
   back to --paper, which is the correct inverse in BOTH themes (near-white on
   dark Marine in light, dark Navy on bright teal in dark). A fixed white would
   read at 2.4:1 on the dark theme's --flow and fail AA (UX-AX-05).
   Height floor is the 44px target minimum (UX-AX-04). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  font: inherit;
  font-weight: 650;
  font-size: .9rem;
  padding: .55em 1.2em;
  border: none;
  border-radius: 6px;
  background: var(--flow);
  color: var(--on-flow, var(--paper));
  text-decoration: none;
  cursor: pointer;
}

.btn:hover { background: var(--flow-hover); color: var(--on-flow, var(--paper)); }
.btn.lg { font-size: 1.05rem; padding: .8rem 1.8rem; }
.btn[disabled] { cursor: progress; opacity: .8; }

.back { font-size: .82rem; font-weight: 650; display: inline-block; margin: 1.6rem 0 .4rem; }

/* ── accessibility utilities ───────────────────────────────────────────── */

/* Visible to a screen reader, not to the eye. The clip-rect pattern is used
   deliberately: display:none would remove the text from the accessibility
   tree, which is the opposite of what this is for (UX-AX-01). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A live region must be in the accessibility tree, and empty, BEFORE its
   content arrives — that is how aria-live works. This file used to carry
   `.live-region:empty { display: none }`, which defeated exactly that:
   display:none removes the element from the tree, so the region was being
   inserted and populated in the same tick and screen readers commonly do not
   announce that. The admin save receipt could go unspoken, breaking the
   surface's own declaration of save: { receipts: true } (ADL F3 — a receipt
   nobody is told about is not a receipt).

   There is deliberately NO replacement rule. An empty div in normal flow is
   already zero-height and occupies no visual space, and the live regions are
   direct children of a plain block container (main.wrap), so no flex/grid gap
   applies to them either. .sr-only exists for content that must be READ but
   not SEEN; these wrappers hold nothing until they hold the visible receipt
   itself, so clipping them would be machinery with no purpose. The correct
   fix here is the absence of a rule. */

/* ── admin form ────────────────────────────────────────────────────────── */

.form-grid { display: grid; gap: 1.5rem; margin-top: 1rem; }

/* A grid item defaults to min-width:auto, so it grows to fit its widest
   content and drags the page into a sideways scroll. Zeroing it is what lets
   the repositories table scroll inside its own box instead. */
.form-grid > * { min-width: 0; }

.field-row { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.field-row.tight { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }

.field-label { display: block; font-weight: 600; margin-bottom: .2rem; }
.field-desc { margin: 0 0 .4rem; }

/* .search carries `margin-left:auto` for the board's filter bar; inside a form
   field it must sit hard left and fill its column. 44px floor is UX-AX-04. */
.field .search {
  display: block;
  width: 100%;
  min-width: 0;
  min-height: 44px;
  margin-left: 0;
}

.sec-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  margin-bottom: .5rem;
}

/* `.chip.tap` used to carry the 44px target-size floor for chips acting as
   actions rather than filters. The distinction did not survive contact: a
   filter is a control too, and the filters were the ones that ended up below
   the floor. The floor now sits on `.chip` itself, where nothing can opt out of
   it — see the note there (UX-AX-04). This rule is deliberately gone rather
   than kept as a no-op, so nothing reads as though the modifier still does
   something. */

/* ── registered repositories editor ────────────────────────────────────── */

/* Five text fields per row cannot fit a narrow viewport; the table scrolls
   inside its own box rather than the page scrolling sideways. */
/* `position: relative` is load-bearing, not decoration. The visually-hidden
   caption and column-header text are position:absolute; without a positioned
   ancestor their containing block is the page, so they sit at their static
   x-offset (~860px) OUTSIDE this box, escape its clip, and drag the whole page
   into a horizontal scroll on a phone. Anchoring them here contains them. */
.repo-scroll { overflow-x: auto; max-width: 100%; position: relative; }

.repo-table { width: 100%; border-collapse: separate; border-spacing: 0 .5rem; }

.repo-table th {
  text-align: left;
  font-size: .6rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted-ink);
  font-weight: 680;
  padding: 0 .5rem .2rem;
  white-space: nowrap;
}

.repo-table td { background: var(--sunk); padding: .5rem; vertical-align: middle; }
.repo-table td:first-child { border-radius: 4px 0 0 4px; }
.repo-table td:last-child { border-radius: 0 4px 4px 0; padding-right: .35rem; }

.repo-table .search {
  display: block;
  width: 100%;
  min-width: 7rem;
  min-height: 44px;
  margin-left: 0;
}

.repo-table .search.wide { min-width: 12rem; }

/* A destructive icon control. 44x44 minimum (UX-AX-04); the glyph is
   aria-hidden and the accessible name comes from aria-label, so the colour
   never carries the meaning on its own (UX-AX-05). Sized at 20px bold so the
   dark theme's --danger on --sunk clears the 3:1 large-text/UI floor. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  font: inherit;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--danger);
  cursor: pointer;
}

.icon-btn:hover {
  background: var(--danger-soft);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
}

/* ── landing page ──────────────────────────────────────────────────────── */

.userbadge { font-size: .85rem; color: var(--muted-ink); text-align: right; }
.userbar { display: flex; justify-content: flex-end; margin-bottom: -1rem; }

.cta-foot {
  margin-top: 2.5rem;
  text-align: center;
  padding: 2rem 0;
  border-top: 1px solid var(--rule);
}

.cta-foot .btn { margin-top: 1rem; }

.landing-points { padding-left: 1.2rem; margin: .5rem 0; }
.landing-points li { margin-bottom: .5rem; }
.landing-points li:last-child { margin-bottom: 0; }

/* ── detail page ───────────────────────────────────────────────────────── */

/* The evidence artifact frame. Held here rather than inline because the two
   colours it needs are the code-surface pair, and a literal GitHub-dark
   #0d1117/#c9d1d9 was what it carried before — those do not theme, and they
   are not ours to mint. --code-bg and --ink are the existing tokens for
   "code surface" and "text on it", and they resolve in both themes. */
.evidence-frame {
  width: 100%;
  height: 180px;
  border: 1px solid var(--rule);
  border-radius: 4px;
  font-family: var(--as-font-mono);
  background: var(--code-bg);
  color: var(--ink);
}

.vd-sub { margin-top: .8rem; padding-top: .8rem; border-top: 1px solid var(--rule); }
.evidence-list { display: grid; gap: 1rem; margin-top: 1rem; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
