/* The Buddy Field Manual — a working manual set in the Concept Stadium faces.
   See design-proposal/DESIGN-PROPOSAL.md; its named rules are this file's
   authority.

   One dominant colour and one accent, whichever theme is on. Alarm and amber
   are not palette, they are readings, so no theme redefines them. There is no
   shadow system in any theme, because print does not float and the source
   design system says so in as many words.

   TWO AXES, and they are independent:

   1. The theme — which palette AND which shapes. The default (Concept
      Stadium) is :root itself, so no attribute means Concept Stadium; every
      other theme is a [data-palette] block below that redeclares the SAME
      token list. The list has two registers: the palette (inks and grounds)
      and the structure (control and plate radii, the button voice, the hover
      treatment). Redeclaring the whole list is the contract: a block that
      names twenty of the twenty-three leaves the other three reading the
      default's, and the themes bleed into one another.
      test/theme.test.js enforces it.
   2. The reading — light or dark. Both sides of every palette live in the
      light-dark() pairs, light value first. The resolved side follows
      `color-scheme`, which is all the reading toggle changes: no [data-theme]
      means the OS decides, [data-theme="light"|"dark"] pins it.

   js/theme.js owns both; index.html stamps both before first paint.

   Faces are not themed. The brand faces are binding: Swear Display (display),
   Filson Pro (text) — both from the Adobe Fonts kit set in index.html — and
   Spline Sans Mono (machine) from Google Fonts. No Inter, no system-font body
   voice, in any theme. */

:root {
  color-scheme: light dark;

  /* ── Concept Stadium: the default theme ───────────────────
     The design system as the agency publishes it, taken literally where the
     earlier house-style transcription took liberties. The grounds keep the
     system's own mapping — the page is its #f2f2f2 canvas and the plate its
     #ffffff card, not the other way round — and the dominant is the system's
     one accent, the mint #5cdfc7, in both readings; there is no darker teal
     anywhere in that palette, so this theme does not invent one. Night is the
     system's own inversion, #000000 under #1c1b1f cards, which is the same
     night house-style transcribed: the two house printings differ by day and
     share the one night the source has. Yellow stays the accent and only the
     accent, which is what keeps the One Voice Rule true: a screen with two
     loud colours has no loud colour.

     The mint holds in both readings because raw mint never speaks: it is
     large decorative ink only, and the clamp below makes the speaking ink
     from it — #006957 by day (5.94:1 on the paper, 6.65:1 on the plate),
     the mint itself at night (12.83:1 on the paper, 10.47:1 on the plate,
     the max(l) clamp is a no-op on a colour this light).

     Two departures from the source system, deliberate: muted ink is #6e6e6e,
     not its #797979 — theirs is a marketing-site caption colour and reads
     4.3:1 at worst here; this one clears 4.55:1 on the grey page and 5.10:1
     on the white plate, and .muted is real prose. And the primary button
     fills with --brand-strong rather than the system's black, because the
     Spot Ink Contract lets a client's own hue land in --brand and the fill
     must follow it. */

  /* grounds: paper (page), plate (tables, figures, cards), wash (row hover, detail rows) */
  --paper: light-dark(#f2f2f2, #000000);
  --plate: light-dark(#ffffff, #1c1b1f);
  --wash:  light-dark(#e6e6e6, #313131);

  /* ink, three strengths */
  --ink:       light-dark(#000000, #f2f2f2);
  --ink-soft:  light-dark(#313131, #cccccc);
  --ink-faint: light-dark(#6e6e6e, #a1a1a1);

  /* rules */
  --rule-soft:  light-dark(#cccccc, #313131);  /* plate borders, row rules */
  --rule-input: light-dark(#313131, #797979);  /* input borders, dotted leaders */

  /* the dominant: per-client overridable, see The Spot Ink Contract.
     js/app.js writes --brand only; --brand-strong derives itself below. */
  --brand: #5cdfc7;                               /* large decorative ink only;
                                                     one value, both readings */
  --brand-strong: light-dark(#006957, #5cdfc7);   /* literal fallback, kept for engines
                                                     without relative colour support */
  --brand-strong: light-dark(
    oklch(from var(--brand) min(l, 0.45) c h),
    oklch(from var(--brand) max(l, 0.72) c h)
  );                                              /* self-deriving AA text ink */
  --on-brand-strong: light-dark(#ffffff, #000000);

  /* the accent */
  --highlight: #ffff00;                           /* the one accent: the highlighter pen */
  --highlight-ink: #000000;                       /* forced ink on highlight, both readings */

  /* ── Structure: the system's shapes ───────────────────────
     Full-round controls and 10px plates, the way the source system draws
     them (its pill is 1.2em against marketing-size type; against this
     portal's 11px button caps that under-rounds, so the pill is held at
     full round instead). Buttons speak in the system's heavy voice and
     hover to the mint with forced black text. The hover ground is not raw
     --brand — a client's dark hue would put black text on a black ground —
     but the same lightness clamp the night reading ink uses, so whatever
     hue the Spot Ink Contract delivers, the hover ground stays light and
     the black ink holds (12.83:1 on the mint itself). */
  --radius-control: 999px;
  --radius-plate: 10px;
  --btn-weight: 800;
  --btn-tracking: 0.08em;
  --btn-pad-x: 20px;
  --btn-hover-bg: #5cdfc7;                        /* literal fallback */
  --btn-hover-bg: oklch(from var(--brand) max(l, 0.72) c h);
  --btn-hover-ink: #000000;
  --btn-hover-border: var(--btn-hover-bg);
  --btn-primary-hover-bg: var(--btn-hover-bg);
  --btn-primary-hover-ink: #000000;

  /* ── Not palette: no theme redefines what follows ─────────
     The readings are the same colour whichever theme is on, because a reader
     who learns that amber means degraded on one palette must not have to
     relearn it on the next. */
  --alarm: #fe4f22;                               /* large alarm marks only, never text */
  --alarm-text: light-dark(#bc3311, #ff7f5b);
  --amber-text: light-dark(#7d5c00, #e0ac3f);

  --scrim: light-dark(rgb(31 40 35 / 0.34), rgb(0 0 0 / 0.6));

  /* faces */
  --display: "swear-display", Georgia, "Times New Roman", serif;
  --text:    "filson-pro", Arial, sans-serif;
  --mono:    "Spline Sans Mono", "Cascadia Mono", Consolas, monospace;
}

/* ── Second printing: the teal-on-white house style ─────────
   The way the house set a page before Concept Stadium became the default,
   kept whole and selectable under its stored id. Grounds inverted from the
   source system — white page, grey plate — with the invented #23a68e teal
   speaking by day and the mint at night, where a 0.45-clamped teal goes
   muddy against black. Print structure: drawn 2px corners, letterspaced
   600-weight button caps, hovers that re-ink instead of filling. */
:root[data-palette="house-style"] {
  --paper: light-dark(#ffffff, #000000);
  --plate: light-dark(#f2f2f2, #1c1b1f);
  --wash:  light-dark(#e6e6e6, #313131);

  --ink:       light-dark(#000000, #f2f2f2);
  --ink-soft:  light-dark(#313131, #cccccc);
  --ink-faint: light-dark(#6e6e6e, #a1a1a1);

  --rule-soft:  light-dark(#cccccc, #313131);
  --rule-input: light-dark(#313131, #797979);

  --brand: light-dark(#23a68e, #5cdfc7);
  --brand-strong: light-dark(#0e6f5c, #5cdfc7);
  --brand-strong: light-dark(
    oklch(from var(--brand) min(l, 0.45) c h),
    oklch(from var(--brand) max(l, 0.72) c h)
  );
  --on-brand-strong: light-dark(#ffffff, #000000);

  --highlight: #ffff00;
  --highlight-ink: #000000;

  --radius-control: 2px;
  --radius-plate: 3px;
  --btn-weight: 600;
  --btn-tracking: 0.14em;
  --btn-pad-x: 16px;
  --btn-hover-bg: transparent;
  --btn-hover-ink: var(--brand-strong);
  --btn-hover-border: var(--brand-strong);
  --btn-primary-hover-bg: color-mix(in oklab, var(--brand-strong) 88%, var(--ink));
  --btn-primary-hover-ink: var(--on-brand-strong);
}

/* ── Field manual: the teal-on-cool-paper original ─────────
   The palette the Field Manual was drawn in, kept whole and now selectable
   rather than assumed. Pine ink on warm-grey paper, the teal dominant, the
   same highlighter. Print structure, same as the second printing: the two
   older themes share their shapes the way they share the highlighter. */
:root[data-palette="field-manual"] {
  --paper: light-dark(#f4f6f1, #131b17);
  --plate: light-dark(#fcfdfa, #1b241f);
  --wash:  light-dark(#e9efe9, #20302a);

  --ink:       light-dark(#1f2823, #e7ece7);
  --ink-soft:  light-dark(#47534c, #b3bdb5);
  --ink-faint: light-dark(#5b675f, #97a39b);

  --rule-soft:  light-dark(#d9dfd4, #2c362f);
  --rule-input: light-dark(#76837a, #5f6f66);

  --brand: light-dark(#23a68e, #2fb99d);
  --brand-strong: light-dark(#0e6f5c, #4fd3b5);
  --brand-strong: light-dark(
    oklch(from var(--brand) min(l, 0.45) c h),
    oklch(from var(--brand) max(l, 0.72) c h)
  );
  --on-brand-strong: light-dark(#ffffff, #0f1f1a);

  --highlight: #fefe00;
  --highlight-ink: #1f2823;

  --radius-control: 2px;
  --radius-plate: 3px;
  --btn-weight: 600;
  --btn-tracking: 0.14em;
  --btn-pad-x: 16px;
  --btn-hover-bg: transparent;
  --btn-hover-ink: var(--brand-strong);
  --btn-hover-border: var(--brand-strong);
  --btn-primary-hover-bg: color-mix(in oklab, var(--brand-strong) 88%, var(--ink));
  --btn-primary-hover-ink: var(--on-brand-strong);
}

/* Set by js/theme.js (and stamped pre-paint by js/theme-preload.js) to pin a
   reading; absent means follow the OS. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }

/* ── Ground ─────────────────────────────────────────────── */
* { box-sizing: border-box; }
/* The hidden attribute must always win, even over display:grid rules
   (login overlay / shell toggling relies on it). */
[hidden] { display: none !important; }
html { height: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--text);
  font-size: 15px;
  line-height: 1.6;
}
::selection { background: var(--highlight); color: var(--highlight-ink); }
:focus-visible { outline: 2px solid var(--brand-strong); outline-offset: 2px; }
a { color: var(--brand-strong); text-underline-offset: 2px; }
mark { background: var(--highlight); color: var(--highlight-ink); padding: 0 4px; }
.sprite { position: absolute; width: 0; height: 0; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ── The skip link ──────────────────────────────────────── */
.skip-link {
  position: absolute; z-index: 80; top: 8px; left: 8px;
  transform: translateY(calc(-100% - 16px));
  background: var(--plate); color: var(--brand-strong);
  border: 1.5px solid var(--brand-strong); border-radius: var(--radius-control);
  padding: 8px 16px; text-decoration: none;
  font-family: var(--text); font-size: 11px; font-weight: 600;
  line-height: 1.3; letter-spacing: 0.14em; text-transform: uppercase;
}
.skip-link:focus { transform: none; }

/* ── Type voices ────────────────────────────────────────── */
/* The Legibility Floor, made structural rather than remembered: the display
   face is only ever attached where a size at or above 22px comes with it, so
   a bare heading can never quietly set Swear Display at body size. Anything
   smaller than a section head is the text face by definition. */
h1, h2 {
  font-family: var(--display); font-size: clamp(22px, 3vw, 28px);
  font-weight: 600; line-height: 1.2; margin: 0;
}
h3, h4 { font-family: var(--text); font-size: 15px; font-weight: 600; margin: 0; }
p { margin: 0; }
.label, .eyebrow, .caps {
  font-family: var(--text); font-size: 11px; font-weight: 600;
  line-height: 1.3; letter-spacing: 0.14em; text-transform: uppercase;
}
.eyebrow { color: var(--ink-soft); }
.view-title {
  font-family: var(--display); font-size: clamp(32px, 4.5vw, 48px);
  font-weight: 500; line-height: 1.08; letter-spacing: -0.01em;
}
/* An h2 inside the document is a section head by definition, so it takes the
   voice without needing the class. The running head follows the same set. */
.section-head, #view h2, .modal h2, .leaf-title {
  font-family: var(--display); font-size: clamp(22px, 3vw, 28px);
  font-weight: 600; line-height: 1.2;
}
.lede { font-size: 19px; line-height: 1.55; }
.muted { color: var(--ink-faint); }
.small { font-size: 13px; line-height: 1.45; }
/* The Verbatim Rule: mono means "this is exactly what the system holds". */
code, .m, .mono {
  font-family: var(--mono); font-style: normal; font-size: 0.92em;
  font-variant-numeric: tabular-nums;
}
.label .m, .eyebrow .m, .caps .m, .toc .m { font-size: inherit; }
.verbatim { font-family: var(--mono); font-size: 12.5px; line-height: 1.7; white-space: pre-wrap; }
/* Prose that must keep the author's line breaks. Not .verbatim: an answer is
   a sentence somebody reads, not machine output. */
.prose-wrap { white-space: pre-wrap; overflow-wrap: break-word; }
.inline-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; }
.nowrap { white-space: nowrap; }
.truncate { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.superseded { text-decoration: line-through; color: var(--ink-faint); }
/* Prose flows on the 8px rhythm; .stack is the one gap utility. */
.stack > * + * { margin-top: 16px; }

/* ── Marks: a printed mark, then a word ─────────────────── */
.mark { width: 12px; height: 12px; flex: none; vertical-align: -1px; }
.mark.live, .mark.syncing { color: var(--brand-strong); }
.mark.degraded { color: var(--amber-text); }
.mark.broken { color: var(--alarm-text); }
.mark.unset { color: var(--ink-faint); }
.mark.alarm-lg { color: var(--alarm); width: 16px; height: 16px; }
/* The mark and its word are one unit: 4px is permitted inside a component. */
.state-mark { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
.state-mark .caps { display: inline-block; }
/* Text carrying a reading, where a drawn mark sits beside it or the word
   itself is the state. Replaces the conditional inline colour one-offs. */
.mark-text.live { color: var(--brand-strong); }
.mark-text.degraded { color: var(--amber-text); }
.mark-text.broken { color: var(--alarm-text); }
.mark-text.unset { color: var(--ink-faint); }

/* ── The shell ──────────────────────────────────────────── */
#shell {
  height: 100dvh;
  display: grid;
  grid-template-rows: auto auto 1fr;
  min-height: 0;
}
.masthead {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 16px 24px; padding: 16px 32px;
  border-bottom: 3px solid var(--ink);
}
.wordmark { width: 78px; aspect-ratio: 207 / 72; height: auto; color: var(--ink); flex: none; display: block; }
.book-title { font-family: var(--display); font-weight: 500; font-style: italic; font-size: 22px; line-height: 1.2; }
.masthead-tools { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 24px; }
.running-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 16px;
  font-size: 13px; line-height: 1.45; color: var(--ink-soft);
}
.rh-state { display: inline-flex; align-items: center; gap: 8px; }
.rh-state.broken { color: var(--alarm-text); }
.rh-state.degraded { color: var(--amber-text); }
.rh-state.syncing, .rh-state.unset { color: var(--ink-soft); }
.role-note { font-style: italic; font-size: 14px; color: var(--ink-soft); }
.client-switcher:empty { display: none; }

/* ── The thumb index: die-cut notches against the band's bottom edge ── */
.thumb-index {
  border-top: 1px solid var(--ink); /* the thin of the thick-thin rule */
  margin-top: 2px;
  border-bottom: 1px solid var(--rule-soft);
  display: flex; align-items: flex-end; gap: 8px;
  overflow-x: auto; padding: 8px 24px 4px;
  /* A grid item's default min-width is auto, which is its content's intrinsic
     width: without this the strip cannot shrink below twelve notches, so
     overflow-x never engages and the tabs push the whole page sideways
     instead of scrolling inside their own band. */
  min-width: 0;
  /* And positioned, so that the visually-hidden state word inside each notch
     is contained by this scroller. Absolutely-positioned descendants resolve
     against the nearest positioned ancestor, so without this they resolve
     against the page, escape the overflow clipping entirely, and a
     screen-reader-only span in the twelfth tab widens the document by the
     width of the tabs it follows. */
  position: relative;
}
.notch {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 16px; white-space: nowrap; text-decoration: none;
  color: var(--ink-soft); background: var(--wash);
  border: 1px solid var(--rule-soft);
  border-bottom: 3px solid transparent;
  border-radius: 2px 2px 0 0;
  font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
}
.notch:hover { color: var(--ink); }
.notch[aria-current="true"] {
  color: var(--ink); background: var(--plate);
  padding-block: 16px 8px; /* the active notch cuts deeper */
  border-bottom-color: var(--brand-strong);
}
.notch .swatch { width: 4px; height: 16px; flex: none; border-radius: 1px; background: var(--swatch, var(--brand)); }

/* ── The document row ───────────────────────────────────── */
/* min-width as well as min-height: the same auto-minimum that stops a grid
   row shrinking vertically stops it shrinking sideways, and a full-bleed
   plate would otherwise widen the page rather than scroll inside itself. */
.layout { display: grid; grid-template-columns: 15rem minmax(0, 1fr); min-height: 0; min-width: 0; }
.contents { overflow-y: auto; padding: 24px 8px 24px 32px; }
.contents > summary { display: none; }
.doc { overflow-y: auto; min-height: 0; }
/* 1100px is the measure prose wants; the widest tables here carry eight columns
   and wanted more than that, so they scrolled inside their card on exactly the
   screens with room to spare. The document takes the extra width when the
   window has it, and prose keeps its own measure through .row and .measure
   rather than through the page being narrow. */
.doc-inner {
  width: min(100%, 1400px); margin-inline: auto;
  padding: 48px 48px 0;
}
.doc-inner > :first-child { margin-top: 0; }
#view > :first-child { margin-top: 0; }

/* ── The contents ───────────────────────────────────────── */
.toc { font-size: 13px; line-height: 1.45; }
.toc-part { color: var(--ink-faint); margin-top: 16px; }
/* `last baseline`, not `baseline`: a two-line entry ("1.2 Knowledge
   amendments") would otherwise strand its leader against the first line. */
.toc a {
  display: flex; align-items: last baseline; gap: 8px;
  padding: 4px 0; text-decoration: none; color: var(--ink-soft);
}
.toc a:hover { color: var(--brand-strong); }
.toc .num { font-family: var(--mono); font-size: 12px; color: var(--brand-strong); font-variant-numeric: tabular-nums; }
.toc .leader { flex: 1; border-bottom: 1px dotted var(--rule-input); transform: translateY(-4px); min-width: 16px; }
.toc a[aria-current="page"] { color: var(--brand-strong); font-weight: 600; }
.toc .manicule { color: var(--brand-strong); align-self: center; }
.toc .flag { align-self: center; }
.toc .count { font-family: var(--mono); font-size: 11px; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* ── Section rows: the measure plus its annotation margin ─ */
.row {
  display: grid; grid-template-columns: minmax(0, 68ch) 15rem;
  column-gap: 48px; row-gap: 24px; margin-top: 48px; align-items: baseline;
}
.row > .fieldnote { grid-column: 2; }
/* A note rendered into a host element the view repaints still belongs in the
   annotation margin. */
.row > .fieldnote-host { grid-column: 2; }
/* Unless there is no prose to annotate. A row holding nothing but a margin note
   was reserving a 68ch column for text that never arrives, and the Audit trail
   stacks three of them around its plate: the note ended up marooned across a
   gap from nothing, three times. With the column dropped the note reads as what
   it is there, a caption on the plate above or below it. */
.row:has(> .fieldnote-host:only-child),
.row:has(> .fieldnote:only-child) { grid-template-columns: minmax(0, 68ch); }
.row > .fieldnote-host:only-child,
.row > .fieldnote:only-child { grid-column: 1; }
/* And the mirror case: a section with no margin note has nothing in the second
   column either, so it takes the whole measure rather than sitting 15rem
   narrower than the plate above it for no reason a reader can see. */
.row > .stack:only-child { grid-column: 1 / -1; }
/* Prose keeps its measure even outside a .row. */
.measure { max-width: 68ch; }
.fieldnote {
  border-top: 2px solid var(--brand-strong); padding-top: 8px;
  font-style: italic; font-size: 14px; line-height: 1.5; color: var(--ink-soft);
}
.fieldnote .label { display: block; font-style: normal; margin-bottom: 4px; color: var(--ink-soft); }
.fieldnote .provenance { display: block; margin-top: 8px; }
.fieldnote.loud { border-top-color: var(--alarm); }
.provenance { font-style: italic; font-size: 14px; line-height: 1.5; color: var(--ink-soft); }
.provenance .m { font-style: normal; }

/* ── The working-view apparatus, compressed ─────────────── */
/* The eyebrow and the title set as one baseline block, the lede folding
   behind a native disclosure after the first visit, so the first table row is
   visible without scrolling at 1366x768. */
.view-header { margin-top: 0; margin-bottom: 24px; }
.view-header .view-title { margin-top: 8px; }
.view-header .btn-row { margin-top: 16px; }
.about { margin-top: 16px; max-width: 68ch; }
details.about > summary { cursor: pointer; color: var(--ink-soft); }
details.about > .lede { margin-top: 8px; }
.section-title {
  font-family: var(--display); font-size: clamp(22px, 3vw, 28px);
  font-weight: 600; line-height: 1.2; color: var(--ink);
  margin: 32px 0 16px; letter-spacing: 0;
  text-transform: none;
}

/* Forms at density: a numbered subsection is a bordered slip inside the text
   measure, set in reading order rather than as a panel in a grid. */
.subsection {
  border: 1px solid var(--rule-soft); border-radius: var(--radius-plate);
  padding: 24px; background: var(--plate);
}
.subsection > .section-head { margin-bottom: 16px; }
.subsection-num { color: var(--brand-strong); margin-right: 8px; }
.subsection > :last-child { margin-bottom: 0; }

/* ── Buttons ────────────────────────────────────────────── */
/* The button's shape and voice belong to the theme: weight, tracking,
   radius, padding and both hover treatments are tokens, declared whole by
   every palette block. The print themes re-ink on hover (transparent
   ground, the reading ink); Concept Stadium fills with the clamped mint
   under forced black. Danger stays alarm-voiced in every theme, because
   alarm is a reading, not palette. */
.btn {
  display: inline-block; cursor: pointer; text-decoration: none;
  font-family: var(--text); font-size: 11px; font-weight: var(--btn-weight);
  letter-spacing: var(--btn-tracking); text-transform: uppercase; line-height: 1.3;
  border-radius: var(--radius-control); padding: 8px var(--btn-pad-x); min-height: 24px;
  background: transparent; color: var(--ink); border: 1.5px solid var(--rule-input);
}
.btn:hover { background: var(--btn-hover-bg); color: var(--btn-hover-ink); border-color: var(--btn-hover-border); }
.btn.primary, .btn-primary {
  background: var(--brand-strong); color: var(--on-brand-strong); border: 1.5px solid transparent;
}
.btn.primary:hover, .btn-primary:hover { background: var(--btn-primary-hover-bg); color: var(--btn-primary-hover-ink); }
.btn.primary:active, .btn-primary:active { background: color-mix(in oklab, var(--btn-primary-hover-bg) 85%, var(--ink)); }
.btn.ghost, .btn-secondary { background: transparent; color: var(--ink); border: 1.5px solid var(--rule-input); }
.btn.ghost:hover, .btn-secondary:hover { background: var(--btn-hover-bg); color: var(--btn-hover-ink); border-color: var(--btn-hover-border); }
.btn.danger { color: var(--alarm-text); border-color: var(--alarm-text); }
.btn.danger:hover { color: var(--alarm-text); border-color: var(--alarm-text); background: color-mix(in oklab, var(--alarm) 8%, transparent); }
.btn.small { padding: 4px calc(var(--btn-pad-x) / 2); }
.btn:disabled { color: var(--ink-faint); border-color: var(--rule-soft); background: transparent; cursor: default; }
.btn-row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }

/* Row actions and in-prose actions: underlined caps, 24px target kept by
   padding with an equal negative margin so the baseline does not move. */
.btn-text {
  background: none; border: 0; cursor: pointer;
  padding: 8px 0; margin-block: -8px;
  font-family: var(--text); font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; line-height: 1.3;
  color: var(--brand-strong); text-decoration: underline;
}
.btn-text:hover { color: var(--ink); }
.btn-text.danger { color: var(--alarm-text); }
.btn-text:disabled { color: var(--ink-faint); cursor: default; text-decoration: none; }
/* Long actions take the sentence-case link voice: letterspaced caps stop
   being a label at five words. */
.btn-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--brand-strong); text-decoration: underline;
  text-underline-offset: 2px;
}
.btn-link:hover { color: var(--ink); }

/* ── Forms and fields ───────────────────────────────────── */
input, select, textarea {
  font-family: var(--text); font-size: 13px; line-height: 1.45;
  width: 100%; color: var(--ink);
  background: var(--plate);
  border: 1.5px solid var(--rule-input); border-radius: var(--radius-control);
  padding: 8px 12px;
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--brand-strong); }
:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--brand-strong); outline-offset: 2px;
}
/* The textarea rule splits by content, not by control: the text face for
   anything a human writes sentences into, mono for configuration, secrets,
   JSON and log-like text. Its radius is clamped because a pill corner on a
   tall box is a broken shape; 16px is the source system's own textarea
   radius, and min() leaves the print themes' 2px alone. */
textarea { min-height: 144px; resize: vertical; font-family: var(--text); line-height: 1.6; border-radius: min(var(--radius-control), 16px); }
textarea.config, textarea[data-verbatim] { font-family: var(--mono); font-size: 12.5px; line-height: 1.7; }
input[type="file"] { padding: 8px; }
/* A checkbox under a pill radius is a circle pretending to be a radio. */
input[type="checkbox"] { width: auto; border-radius: 2px; }
label.field, .field { display: block; margin-bottom: 16px; }
label.field > span, .field > label {
  display: block; margin-bottom: 8px; color: var(--ink-soft);
  font-family: var(--text); font-size: 11px; font-weight: 600;
  line-height: 1.3; letter-spacing: 0.14em; text-transform: uppercase;
}
.field-hint, .hint { font-style: italic; font-size: 14px; line-height: 1.5; color: var(--ink-soft); margin-top: 4px; }
/* A hint reporting something the operator must act on, not describing the field. */
.field-hint.warn { color: var(--amber-text); font-style: normal; }
.inline-fields { display: grid; grid-template-columns: 1fr 1fr; gap: 0 24px; }
/* Three fields, evenly. Two-up would leave the third alone on a second row. */
.inline-fields.cols-3 { grid-template-columns: repeat(3, 1fr); }
/* A membership row: two pickers and the control that removes them. `auto` on
   the last column keeps the ✕ the width of a ✕ rather than a third of the row,
   and .inline-fields would otherwise wrap it onto a line of its own. */
.inline-fields.inline-row { grid-template-columns: 1fr 1fr auto; align-items: center; }
.checkbox-row { display: flex; align-items: flex-start; gap: 8px; padding-top: 4px; }
.checkbox-row input[type="checkbox"] { width: auto; margin-top: 4px; }
/* A checkbox standing as its own field in a form, rather than inside one. It
   needs the gap a .field carries before whatever follows it. */
.card > .checkbox-row { padding-bottom: 16px; }
/* The filter row lives in the section header, outside the repainted plate. */
.filters, .filter-row { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 24px; align-items: flex-end; }
.filters select, .filters input, .filter-row select, .filter-row input { width: auto; min-width: 16ch; }
.filters .btn, .filter-row .btn { align-self: flex-end; }

/* ── Plates and tables ──────────────────────────────────── */
.plate {
  background: var(--plate); border: 1px solid var(--rule-soft); border-radius: var(--radius-plate);
  overflow-x: auto;
}
.plate-wrap { margin-top: 24px; }
/* Liveness is signalled on the wrapper, not on the rows: replaceChildren()
   destroys child transitions, so a tint on the container is the only thing
   that survives a full repaint. Triggered from JS, never resident. */
.plate-wrap.reprinting { animation: reprint 700ms ease-out; }
@keyframes reprint {
  from { background: color-mix(in oklab, var(--brand) 14%, var(--paper)); }
  to { background: transparent; }
}
/* The card is a plate until Phase 5 dissolves the last of them into prose. */
.card {
  background: var(--plate); border: 1px solid var(--rule-soft); border-radius: var(--radius-plate);
  padding: 24px; margin-bottom: 24px;
}
.card > table, .plate > table { margin: 0; }
/* Eight views append a <table> straight into a .card rather than through
   plate(), so those tables had no scroller and a wide one crushed its columns
   to fit. The scroller is the fallback, not the plan: with the document at its
   full width these tables fit, and the minimum is low enough that scrolling
   only starts on a genuinely narrow window. Scoped by :has, so a card of fields
   never grows a scrollbar it has no use for. */
.card:has(> table) { overflow-x: auto; }
.card > table { min-width: 34rem; }
/* An id or a hash is one token. Broken across four lines to fit a column it is
   unreadable and cannot be selected in one gesture, and the plate already
   scrolls, so the table grows and the id stays whole. */
td.mono, td .mono { white-space: nowrap; }
.grid { display: grid; gap: 24px; }
.grid.cols-2 { grid-template-columns: repeat(auto-fill, minmax(336px, 1fr)); }

table { width: 100%; border-collapse: collapse; }
/* Below its minimum the plate scrolls inside itself rather than crushing the
   columns; plate() gives that scroller a tabindex, a role and a name. */
.plate > table { min-width: 44rem; }
th, td { padding: 8px 16px; text-align: left; vertical-align: top; font-size: 13px; line-height: 1.45; }
th {
  font-family: var(--text); font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft);
  border-bottom: 2px solid var(--ink); white-space: nowrap;
}
tbody tr { border-bottom: 1px solid var(--rule-soft); }
tbody tr:last-child { border-bottom: none; }
/* One target per plate: the whole row opens the leaf, so the wash is earned. */
tr.clickable, tbody tr.entry { cursor: pointer; }
tr.clickable:hover, tbody tr.entry:hover { background: var(--wash); }
tr.clickable > td:first-child, tbody tr.entry > td:first-child { border-left: 2px solid transparent; }
/* Reading marks: a change bar on entries recorded since the last visit. */
tr.since > td:first-child { border-left-color: var(--brand-strong); }
td.actions, td.act { text-align: right; white-space: nowrap; }
td.time { font-family: var(--mono); font-size: 12.5px; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink-soft); }
/* Scope tags are unboxed: letterspaced caps separated by middots. */
td.scopes, .scope-tags {
  font-size: 11px; font-weight: 600; line-height: 1.45;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft);
}
.tag { font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft); }
.tag + .tag::before { content: " \00b7 "; }
/* A composed message shown before it is sent. Monospaced and wrapping, because
   what is being read is the letter as the recipient gets it, and a consent URL
   is a long unbroken token that must not run off the side of the box. */
.mono-body {
  font-family: var(--mono); font-size: 12.5px; line-height: 1.6;
  white-space: pre-wrap; overflow-wrap: break-word; resize: vertical;
}

/* A removable set: the same tag, laid out as its own row of chips. The middot
   between adjacent tags is suppressed because the gap already separates them
   and the remove control reads as the end of one. */
.chip-row { display: flex; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 16px; min-height: 24px; align-items: center; }
.chip-row .tag + .tag::before { content: none; }
.chip-row .tag { display: inline-flex; align-items: center; gap: 4px; }
.chip-x {
  background: none; border: 0; padding: 0 2px; cursor: pointer; line-height: 1;
  font-size: 14px; color: var(--ink-faint);
}
.chip-x:hover { color: var(--alarm-text); }
.cite { display: block; margin-top: 4px; font-size: 13px; color: var(--ink-soft); }

/* ── The expanded detail row: a set annotation ──────────── */
tr.detail-row > td, tr.detail > td { background: var(--wash); padding: 16px; }
.detail-note { font-style: italic; font-size: 14px; line-height: 1.5; color: var(--ink-soft); max-width: 52ch; }
.detail-fieldnote { margin-top: 16px; max-width: 52ch; }
.meta, .kv {
  display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 4px 16px;
  margin-top: 16px; align-items: baseline;
}
.meta dt, .kv dt {
  font-size: 11px; font-weight: 600; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-faint);
}
.meta dd, .kv dd {
  margin: 0; font-family: var(--mono); font-size: 12.5px; line-height: 1.7;
  font-variant-numeric: tabular-nums; word-break: break-word;
}
.meta dd.err, .kv dd.err { color: var(--alarm-text); }
.detail-actions { display: flex; flex-wrap: wrap; gap: 16px 24px; margin-top: 16px; }
.eval-run { padding: 8px 0; border-bottom: 1px solid var(--rule-soft); }

/* ── The folio line (pager) ─────────────────────────────── */
.pager, .folio {
  display: flex; flex-wrap: wrap; align-items: center; gap: 16px 24px;
  margin-top: 16px;
}
.pager .pager-status, .folio-count {
  font-size: 13px; line-height: 1.45; font-variant-numeric: tabular-nums; color: var(--ink-soft);
}
.pager select, .folio select { width: auto; min-width: 8ch; }
.pager .btn-row, .folio-nav { margin-left: auto; display: flex; gap: 8px; }

/* ── Figures ────────────────────────────────────────────── */
/* The wordmark is a link in two places and decoration in none. It keeps the
   logo's own colour rather than taking link ink. */
.wordmark-link { display: inline-flex; color: inherit; text-decoration: none; }

.figure {
  background: var(--plate); border: 1px solid var(--rule-soft); border-radius: var(--radius-plate);
  padding: 24px; margin-top: 32px;
  /* The reading on hover is positioned against this box. */
  position: relative;
}
/* Outside the viewBox on purpose: a label drawn in plate units shrinks with the
   plate, so it was smallest in the side-by-side pair where the bars are already
   hardest to read. In CSS pixels it is one size at every figure width. */
.fig-tip {
  position: absolute; z-index: 2; pointer-events: none;
  transform: translate(-50%, -100%); margin-top: -12px;
  background: var(--ink); color: var(--paper);
  padding: 4px 8px; border-radius: 2px;
  font-size: 12px; line-height: 1.4; white-space: nowrap;
}
.figure figcaption {
  margin-top: 16px; border-top: 2px solid var(--brand-strong); padding-top: 8px;
  font-size: 14px; line-height: 1.5;
}
.figure figcaption em { color: var(--ink-soft); }
.fig-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
.fig-pair .figure { margin-top: 24px; }
.chart-legend { display: flex; gap: 24px; margin-top: 16px; flex-wrap: wrap; }
.chart-legend-item { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: var(--ink-soft); }
.legend-swatch { width: 16px; height: 8px; flex: none; border-radius: 1px; }

/* ── KPI readings: every reading is a door ─────────────── */
.readings, .grid.tiles { display: flex; flex-wrap: wrap; gap: 32px 64px; margin-top: 24px; }
.kpi, .tile { display: block; text-decoration: none; color: var(--ink); max-width: 30ch; }
.kpi-num, .tile .tile-value {
  font-family: var(--mono); font-weight: 600; font-size: 32px;
  line-height: 1.2; font-variant-numeric: tabular-nums; color: var(--ink);
}
/* A reading is one thing and reads as one line. Not every tile holds a short
   number — "7 Aug 2026, 18:45" is a reading too, and broken over two lines it
   stops looking like a value and starts looking like a sentence. The tile grows
   to fit rather than the timestamp wrapping, which the flex row allows. */
.tile .tile-value { white-space: nowrap; }
.tile:has(> .tile-value) { max-width: none; }
a.kpi:hover .kpi-num { color: color-mix(in oklab, var(--brand-strong) 88%, var(--ink)); }
.kpi .label, .tile .tile-label {
  display: block; color: var(--ink-soft); margin-top: 4px;
  font-family: var(--text); font-size: 11px; font-weight: 600;
  line-height: 1.3; letter-spacing: 0.14em; text-transform: uppercase;
}
.kpi .provenance { font-size: 13px; color: var(--ink-faint); margin-top: 4px; }

/* ── The focal reading ──────────────────────────────────── */
.focal-inset { float: left; margin: 8px 32px 16px 0; max-width: min(100%, 34ch); }
.focal-num {
  display: flex; align-items: center; gap: 16px;
  font-family: var(--display); font-weight: 700;
  font-size: clamp(56px, 6vw, 88px); line-height: 1;
  color: var(--brand-strong);
  border-bottom: 4px solid var(--brand); padding-bottom: 8px;
}
.focal-unit { font-family: var(--display); font-weight: 600; font-size: clamp(22px, 3vw, 28px); }
.focal-inset figcaption { margin-top: 8px; max-width: 30ch; }

/* ── States ─────────────────────────────────────────────── */
/* Loading: galley slugs at the true type scale of what they replace. */
.galley { max-width: 68ch; }
.galley > * + * { margin-top: 8px; }
.slug { display: block; background: var(--rule-soft); border-radius: 2px; }
.slug-lede { height: 19px; width: 96%; }
.slug-body { height: 15px; }
.galley .slug-body:nth-child(2) { width: 100%; }
.galley .slug-body:nth-child(3) { width: 97%; }
.galley .slug-body:nth-child(4) { width: 62%; }
.slug-plate { padding: 16px; margin-top: 24px; }
.slug-plate > * + * { margin-top: 24px; } /* 13px slug plus 24px gap holds the 36px row budget */
.slug-row { height: 13px; }
.slug-plate .slug-row:nth-child(1) { width: 88%; }
.slug-plate .slug-row:nth-child(2) { width: 76%; }
.slug-plate .slug-row:nth-child(3) { width: 92%; }
.slug-plate .slug-row:nth-child(4) { width: 68%; }
.slug-plate .slug-row:nth-child(5) { width: 81%; }

/* Empty and unconfigured: an editorial note, never a blank plate. */
.empty { max-width: 68ch; color: var(--ink); text-align: left; padding: 0; }
.empty > * + * { margin-top: 8px; }
.empty-head { display: flex; align-items: center; gap: 8px; color: var(--ink-faint); }
.empty .btn { margin-top: 24px; }

/* Broken: the errata slip. Cause, consequence, fix with a citation. */
.errata, .banner {
  background: var(--plate); color: var(--ink);
  border: 1px solid var(--rule-soft); border-top: 3px solid var(--alarm);
  border-radius: 2px; padding: 16px 24px; margin-top: 24px; max-width: 68ch;
}
.errata-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.errata-label { background: var(--highlight); color: var(--highlight-ink); padding: 4px 8px; }
.errata p + p, .banner p + p { margin-top: 8px; }
.btn-errata { margin-top: 16px; }
.banner.info { border-top-color: var(--brand-strong); }
.banner.warning { border-top-color: var(--amber-text); }
.banner.error { border-top-color: var(--alarm); }

/* The onboarding checklist. */
.checklist { list-style: none; padding: 0; margin: 0; max-width: 68ch; }
.checklist > li {
  display: grid; grid-template-columns: 9rem minmax(0, 1fr);
  gap: 4px 24px; padding: 16px 0; border-bottom: 1px solid var(--rule-soft);
  align-items: start; /* the state reads against the step's first line */
}
.step-state { display: flex; align-items: center; gap: 8px; color: var(--ink-faint); }
.step-state.done { color: var(--brand-strong); }
.step-proves { font-style: italic; font-size: 14px; line-height: 1.5; color: var(--ink-soft); margin-top: 4px; }
.checklist .btn { margin-top: 16px; }

/* ── Badges: the mark plus its word, unboxed (Phase 3 draws the real mark) ── */
.badge {
  display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
  font-family: var(--text); font-size: 11px; font-weight: 600;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-soft);
}
.badge .dot { width: 8px; height: 8px; flex: none; background: currentColor; }
.badge.good { color: var(--brand-strong); }
.badge.good .dot { border-radius: 50%; }
.badge.info { color: var(--brand-strong); }
.badge.info .dot { border-radius: 50%; }
.badge.warning { color: var(--amber-text); }
.badge.critical { color: var(--alarm-text); }

/* ── Modals: the tipped-in leaf ─────────────────────────── */
.modal-overlay {
  position: fixed; inset: 0; z-index: 50;
  background: var(--scrim);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 8vh 16px 16px;
  overflow-y: auto;
}
.modal, .leaf {
  background: var(--plate);
  border: 1.5px solid var(--ink);
  border-radius: var(--radius-plate);
  width: min(560px, 100%);
  padding: 24px;
}
.modal h2 { margin-bottom: 16px; }
.modal .modal-actions { display: flex; justify-content: flex-end; gap: 16px; margin-top: 24px; }
/* Opt-in, not a default: only a modal whose content is a table/list needs
   either. .modal-overlay's own scroll drags the title and actions away with
   it, so a long list needs its own scroller instead — everything outside
   .modal-scroll (title, intro, bulk toolbar, actions) stays pinned. */
.modal.modal-wide { width: min(820px, 95vw); }
.modal-scroll { max-height: 55vh; overflow-y: auto; }
.modal-scroll table input[type="text"] { width: 100%; }
.leaf-part { margin-top: 24px; color: var(--ink-soft); }
.leaf-q { margin-top: 16px; font-size: 19px; line-height: 1.55; }
.leaf-a { margin-top: 8px; white-space: pre-wrap; overflow-wrap: break-word; }
.leaf-body input[type="search"] { width: 100%; }

/* The index, set as an index sets it: dotted leader, mono locator. */
.index-list { list-style: none; padding: 0; margin-top: 8px; }
.index-list a {
  display: flex; align-items: last baseline; gap: 8px;
  padding: 4px 8px; text-decoration: none; color: var(--ink);
  font-size: 13px; line-height: 1.45;
}
.index-list a:hover { color: var(--brand-strong); }
.index-list .leader { flex: 1; border-bottom: 1px dotted var(--rule-input); transform: translateY(-4px); min-width: 16px; }
.index-list .loc { font-family: var(--mono); font-size: 12.5px; color: var(--brand-strong); font-variant-numeric: tabular-nums; }
.index-list .cited { color: var(--ink-faint); }
.index-list a[aria-current="true"] { background: var(--wash); color: var(--brand-strong); }
.index-list .manicule { color: var(--brand-strong); align-self: center; }

/* ── Toasts: paper slips ────────────────────────────────── */
#toast-root { position: fixed; bottom: 24px; right: 24px; z-index: 100; display: flex; flex-direction: column; gap: 8px; }
.toast, .slip {
  width: min(320px, 100%);
  background: var(--plate); border: 1px solid var(--rule-soft);
  border-top: 2px solid var(--brand-strong); border-radius: 2px;
  padding: 8px 16px 16px;
  font-size: 15px; line-height: 1.6;
}
.toast .label, .slip .label { display: block; color: var(--ink-soft); margin-bottom: 4px; }
.toast.error { border-top-color: var(--alarm-text); }

/* ── Login ──────────────────────────────────────────────── */
#login-overlay {
  position: fixed; inset: 0; z-index: 60; overflow-y: auto;
  background: var(--paper);
  display: flex; flex-direction: column;
}
.login-masthead { border-bottom: 3px solid var(--ink); }
.login-main {
  flex: 1; display: flex; align-items: flex-start; justify-content: center;
  padding: 48px 32px;
}
.login-card {
  background: var(--plate);
  border: 1px solid var(--rule-soft);
  border-top: 3px solid var(--brand);
  border-radius: var(--radius-plate);
  padding: 0 24px 24px;
  width: min(26rem, 100%);
}
.login-card::before {
  content: ""; display: block;
  border-top: 1px solid var(--rule-soft);
  margin: 2px -24px 24px;
}
.login-card .wordmark { width: 132px; margin-bottom: 24px; }
.card-client { margin-bottom: 4px; }
.card-title { font-family: var(--display); font-size: clamp(22px, 3vw, 28px); font-weight: 500; line-height: 1.2; }
.card-sub { font-style: italic; color: var(--ink-soft); margin-top: 4px; }
.who { margin-top: 8px; }
.login-card .field { margin-top: 16px; margin-bottom: 0; }
.login-card .btn-primary { width: 100%; margin-top: 24px; text-align: center; }
.login-card .btn-secondary { width: 100%; margin-top: 16px; text-align: center; }
/* The refused state in the errata voice: mark, word, cause, fix. */
.error-line { margin-top: 16px; color: var(--alarm-text); }
.error-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.error-head .label { color: var(--alarm-text); }
/* The second door, under a rule that reads as a rule. */
.or-rule { display: flex; align-items: center; gap: 16px; margin-top: 24px; color: var(--ink-faint); }
.or-rule::before, .or-rule::after { content: ""; flex: 1 1 0; border-top: 1px solid var(--rule-soft); }

/* ── Colophon ───────────────────────────────────────────── */
.colophon { margin-top: 64px; border-top: 3px solid var(--ink); padding-top: 2px; }
.colophon-inner {
  border-top: 1px solid var(--ink);
  padding: 24px 0 48px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 16px 24px;
}
.colophon .wordmark { width: 66px; }
.colophon .muted { font-style: italic; font-size: 14px; color: var(--ink-faint); }
.login-colophon { margin-top: 0; padding: 0 32px; }
.login-colophon .colophon-inner { padding-bottom: 24px; }

/* ── The interview (3.3 Playground) ─────────────────────── */
/* No arithmetic against chrome height. The old rule was
   `calc(100vh - 170px)`, which hardcoded the height of a sidebar that no
   longer exists and broke on the mobile URL-bar dance besides. A dvh
   fraction is measured against the viewport, not against the chrome, so
   nothing here has to know what is above it. */
/* The transcript scrolls with the document and the composer sticks to the foot
   of the window, the way every chat surface behaves. It used to be a 60dvh box
   with its own scrollbar, which put the input below the fold on arrival: the
   one control the screen exists for was the one thing you had to go looking
   for. Sticky needs no measuring of where the box happens to start. */
.chat-layout { display: flex; flex-direction: column; min-height: 60dvh; }
.chat-messages { flex: 1; min-height: 0; padding-bottom: 24px; }
.chat-msg { max-width: 68ch; padding: 16px 0; border-bottom: 1px dotted var(--rule-input); }
/* The asker's turn sits right, the way every chat surface sets it, so the two
   voices are told apart before either is read. The block moves; the text inside
   stays ranged left, because centred or right-ragged prose is harder to read
   and a question can run long. */
.chat-msg.user { margin-left: auto; border-bottom: none; }
.chat-msg.user .speaker { text-align: right; }
.chat-msg .speaker {
  display: block; margin-bottom: 4px;
  font-family: var(--text); font-size: 11px; font-weight: 600;
  line-height: 1.3; letter-spacing: 0.14em; text-transform: uppercase;
}
.chat-msg.user .speaker { color: var(--ink-faint); }
.chat-msg.user { font-size: 19px; line-height: 1.55; }
.chat-msg.bot .speaker { color: var(--brand-strong); }
.chat-msg.bot.error { border-bottom: none; }
.chat-meta { font-style: italic; font-size: 14px; line-height: 1.5; color: var(--ink-soft); margin-top: 8px; }
.chat-meta details { margin-top: 8px; }
.chat-meta pre {
  background: var(--wash); border-radius: 2px; padding: 8px; margin-top: 8px;
  overflow-x: auto; max-height: 240px;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.7; font-style: normal;
}
.chat-input-row {
  display: flex; gap: 16px; padding-top: 16px; border-top: 2px solid var(--ink);
  position: sticky; bottom: 0; z-index: 1;
  /* Opaque, because the transcript scrolls underneath it. */
  background: var(--paper); padding-bottom: 24px;
}
.chat-input-row input { flex: 1; }

/* ── Logs ───────────────────────────────────────────────── */
.log-lines { font-family: var(--mono); font-size: 12.5px; line-height: 1.7; color: var(--ink-soft); }
.log-lines div { padding: 4px 0; }
.log-lines .ts { color: var(--ink-faint); margin-right: 8px; }

/* ── The tree (ii. Agency) ──────────────────────────────── */
.tree-row { display: flex; align-items: baseline; gap: 8px; padding: 4px 0; }
.tree-num { font-family: var(--mono); font-size: 12.5px; color: var(--brand-strong); font-variant-numeric: tabular-nums; }
.tree-leader { flex: 1; border-bottom: 1px dotted var(--rule-input); transform: translateY(-4px); min-width: 16px; }

/* ── Accordions (1.3 BrandOS) ───────────────────────────── */
details.section > summary {
  cursor: pointer;
  font-family: var(--display); font-size: clamp(22px, 3vw, 28px);
  font-weight: 600; line-height: 1.2; margin-top: 32px;
}
details.section > .section-body { margin-top: 16px; }

/* ── Connections ────────────────────────────────────────── */
.conn-card { display: flex; flex-direction: column; gap: 8px; }
.conn-card .conn-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
.conn-card .conn-title { font-family: var(--display); font-size: 22px; font-weight: 600; line-height: 1.2; }
.conn-card .conn-type { font-size: 13px; color: var(--ink-faint); }
.conn-card .conn-msg { font-style: italic; font-size: 14px; line-height: 1.5; color: var(--ink-soft); min-height: 16px; }
.conn-card .conn-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; }

/* ── Responsive ─────────────────────────────────────────── */
@media (max-width: 1100px) {
  .row { grid-template-columns: minmax(0, 68ch); }
  .row > .fieldnote, .row > .fieldnote-host { grid-column: 1; max-width: 52ch; }
  .fig-pair { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
  .layout { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
  details.contents {
    overflow-y: auto; max-height: 40dvh;
    padding: 16px 24px; border-bottom: 1px solid var(--rule-soft);
  }
  .contents > summary { display: list-item; cursor: pointer; }
  .toc { margin-top: 8px; }
  .doc-inner { padding: 32px 24px 0; }
  .masthead { padding: 16px 24px; }
  .login-main { padding: 32px 24px; }
  .login-colophon { padding: 0 24px; }
  .grid.cols-2 { grid-template-columns: 1fr; }
  .inline-fields { grid-template-columns: 1fr; }
  .focal-inset { float: none; margin: 0 0 16px; max-width: 100%; }
  .checklist > li { grid-template-columns: minmax(0, 1fr); }
  #toast-root { left: 16px; right: 16px; bottom: 16px; }
  .toast, .slip { width: auto; }
}
@media (max-width: 480px) {
  .doc-inner { padding: 24px 16px 0; }
  .masthead { padding: 16px; }
  .login-main { padding: 24px 16px; }
  .login-colophon { padding: 0 16px; }
  .modal-overlay { padding: 4vh 8px 16px; }
  .meta, .kv { grid-template-columns: minmax(0, 1fr); }
}

/* ── Motion: print does not animate, pages settle ───────── */
@media (prefers-reduced-motion: no-preference) {
  .btn, .btn-text, .btn-link, a, tr.clickable, tbody tr.entry {
    transition: color 120ms ease-out, background-color 120ms ease-out, border-color 120ms ease-out;
  }
  /* The one orchestrated moment: the incoming view settles, once. */
  #view > * { animation: settle 160ms ease-out; }
  .modal-overlay { animation: fade-in 120ms ease-out; }
  .modal, .toast, .login-card { animation: settle 160ms ease-out; }
  @keyframes fade-in { from { opacity: 0; } }
  @keyframes settle { from { opacity: 0; transform: translateY(6px); } }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
