/* ============================================================
   Localization OS — "Kiln" design system (dark-first, warm graphite)
   Palette: warm-graphite neutral surfaces + one fired-terracotta
   identity hue, growth-green / amber-hold / vermilion-stop state
   colors. Border-first, effectively flat. See DESIGN.md — that
   spec is normative; this file implements it (token NAMES kept so
   the 48 templates / 9 JS files need no renames, only VALUES change).
   ============================================================ */

/* Self-hosted Kiln typefaces (offline-first — NO Google Fonts link /
   @import anywhere). Single variable woff2 per family (latin subset covers
   en/es/fr/de). See app/static/fonts/README.md for sources + licenses. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url("/static/fonts/space-grotesk-latin-wght-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Hanken Grotesk";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/hanken-grotesk-latin-wght-normal.woff2") format("woff2");
}
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("/static/fonts/jetbrains-mono-latin-wght-normal.woff2") format("woff2");
}

/* ------------------------------------------------------------------
   SKIN typefaces (Instrument + Ledger). Same self-hosted, offline-first
   posture as the Kiln faces above (NO Google Fonts link/@import). These are
   only requested when a non-default skin is active — the browser fetches a
   woff2 lazily the first time a rule that uses the family actually matches, so
   a Kiln user (the default) downloads none of them. They are therefore NOT
   preloaded in base.html (only the two default-skin faces are); the tradeoff is
   a one-frame FOUT the first time a skin user loads, versus forcing 5 extra
   font fetches on every Kiln user forever. Same latin (+ U+2190-2194 arrows)
   subset as the Kiln faces — see app/static/fonts/README.md for sources +
   regeneration. New filenames per the immutable-fonts rename policy. */
/* Instrument: IBM Plex Sans (variable wdth+wght; wdth defaults to 100 = normal)
   + IBM Plex Mono (static 400/500 — no upstream variable). */
@font-face {
  font-family: "IBM Plex Sans";
  font-style: normal;
  font-weight: 100 700;
  font-display: swap;
  src: url("/static/fonts/ibm-plex-sans-latin-wght-normal.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/static/fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
}
/* Ledger: Spectral serif display (static 400/600/700 — Spectral has no variable)
   + Public Sans body/data (variable wght). Public Sans carries NO arrow glyphs
   upstream (like Hanken), so Ledger's --data-font stack falls the U+2192 in locale
   pairs back to the already-bundled JetBrains Mono rather than a system font
   (the U+2192 lesson, README). */
@font-face {
  font-family: "Spectral";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("/static/fonts/spectral-latin-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Spectral";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/static/fonts/spectral-latin-600-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Spectral";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/static/fonts/spectral-latin-700-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("/static/fonts/public-sans-latin-wght-normal.woff2") format("woff2");
}

:root {
  --bg: #17130f;
  --bg-2: #1a1510;
  --panel: #201a14;
  --panel-2: #2a221a;
  --elev: #2a221a;
  --border: #3a2f24;
  --border-soft: #291f16;
  --text: #f2ece4;
  --text-dim: #d6ccbe;
  --muted: #ad9d8c;

  --primary: #e58a5f;
  --primary-2: #ec9d77;
  --primary-soft: rgba(229, 138, 95, .16);
  --primary-ink: #1c0f07;
  --ring: rgba(229, 138, 95, .45);
  --primary-border: rgba(229, 138, 95, .42);

  --success: #63c48c;
  --success-soft: rgba(99, 196, 140, .15);
  --success-border: rgba(99, 196, 140, .4);
  --warn: #e6b455;
  --warn-soft: rgba(230, 180, 85, .15);
  --warn-border: rgba(230, 180, 85, .4);
  --danger: #f0796b;
  --danger-border: rgba(240, 121, 107, .45);

  --radius: 11px;
  --radius-sm: 9px;
  /* --radius-xs: the small-element tier below --radius-sm (chips, code, kbd,
     prompt boxes, rowform selects, docs-toc links, tag pills, mono readouts). A
     TOKEN so the small-element family follows each skin's plate: Kiln 6px,
     Instrument tightens to 4px, Ledger squares to 2px. Under Kiln this is a
     DELIBERATE harmonization, not a byte-identical swap: the family's former
     6px members keep their value, and the former 5px members (code, kbd,
     .method, .tag-chip, .tag-hint) round up 1px so the whole small-element
     tier shares one radius. Leaves --radius / --radius-sm consumers alone. */
  --radius-xs: 6px;
  /* Two-step shadow vocabulary (Kiln is border-first, effectively flat):
     --shadow = "Float" (true overlays only), --shadow-sm = "Whisper" (optional
     resting shade under a card; omitting it is always fine). */
  --shadow: 0 10px 28px -14px rgba(0, 0, 0, .5);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .25);

  --hover-border: #4a3c2e;
  --scrollbar-hover: #4a3c2e;
  --danger-border-solid: #5c352b;
  --term-tag: #8fdcb0;

  /* Editor row-state tints (dirty = unsaved edit, failed = save error). The
     -hover step is deliberately STRONGER than the resting tint so a status row
     still gives hover feedback without ever falling back to the neutral
     .table row-hover (which would hide the status exactly when pointed at). */
  --row-dirty: rgba(229, 138, 95, .16);
  --row-dirty-hover: rgba(229, 138, 95, .26);
  --row-failed: rgba(240, 121, 107, .10);
  --row-failed-hover: rgba(240, 121, 107, .18);

  /* Type tokens (new). Space Grotesk speaks; Hanken Grotesk reads; JetBrains
     Mono counts. The var(--mono, monospace) fallback sites already existed. */
  --font-body: "Hanken Grotesk", system-ui, sans-serif;
  --font-display: "Space Grotesk", system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, monospace;
  /* Code-vs-data split (skins review): --mono is the CODE face — ALWAYS a true
     monospace in every skin (code, pre.code, kbd, key/URL readouts need aligned
     fixed-advance runs). --data-font is the DATA face (locale pairs, counts:
     .data-mono, .grid td.num, .lp-code, .pp-matrix) — normally the same monospace,
     but a skin may point it at a proportional face with lining tabular figures
     (Ledger: Public Sans, per its plate). */
  --data-font: "JetBrains Mono", ui-monospace, monospace;
  /* Display-face letter-spacing (h1 / wordmark). A TOKEN because the tracking that
     flatters Kiln's wide grotesque (-0.02em) is wrong for a skin whose display face
     is a serif (Ledger's Spectral wants ~0) — a serif inheriting negative display
     tracking blindly looks cramped. Each skin sets its own; the rule reads the token. */
  --display-tracking: -.02em;

  /* Spacing scale (copy/density audit, wave 1): every gap in the app should trace
     to one of these instead of a hand-rolled literal. --readable caps prose/label
     line length so helper text stays legible on the ultrawide .content column. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;
  --readable: 68ch;

  /* Native form-control / scrollbar rendering hint (matches the dark surfaces). */
  color-scheme: dark;
}

/* ============================================================
   Light theme (Kiln neutral paper). Opt-in only: applied via
   html[data-theme="light"] (set by the no-FOUC script in base.html from
   localStorage 'loc_theme'). :root stays the dark default, so with no theme
   set — or no JS — rendering is the Kiln dark UI.

   EVERY token in :root is redefined here (parity guard: verify_ui_theme.py).
   Light paper is TRUE NEUTRAL (chroma ~0) — never cream/sand/beige; Kiln's
   warmth lives only in the terracotta accent and the type. The terracotta is
   darkened to #a8451f so accent text and the white-on-primary button clear AA
   on white. State colors are darkened for legibility on paper. All contrast
   ratios are verified programmatically (DESIGN.md §2; re-checked by the
   throwaway contrast script during migration). Hardcoded non-token colors that
   would misread on light get scoped overrides at the end of the file.
   ============================================================ */
html[data-theme="light"] {
  --bg: #f4f4f5;
  --bg-2: #ececec;
  --panel: #ffffff;
  --panel-2: #ececec;
  --elev: #ffffff;
  --border: #dcdadb;
  --border-soft: #e8e6e7;
  --text: #1a1613;
  --text-dim: #403a34;
  --muted: #5c554e;

  --primary: #a8451f;
  /* Accent-as-text darkened one step past the fill: #a8451f measures only
     4.37:1 on the sidebar-active composite (--primary-soft wash over --bg-2).
     #9e3e1b clears 4.89:1 there and >=5.6:1 everywhere else it's used. */
  --primary-2: #9e3e1b;
  --primary-soft: rgba(168, 69, 31, .10);
  --primary-ink: #ffffff;
  --ring: rgba(168, 69, 31, .35);
  --primary-border: rgba(168, 69, 31, .34);

  --success: #0f6f47;
  --success-soft: rgba(15, 111, 71, .10);
  --success-border: rgba(15, 111, 71, .34);
  --warn: #82530a;
  --warn-soft: rgba(130, 83, 10, .10);
  --warn-border: rgba(130, 83, 10, .32);
  --danger: #bb2e21;
  --danger-border: rgba(187, 46, 33, .40);

  --radius: 11px;
  --radius-sm: 9px;
  --radius-xs: 6px;
  --shadow: 0 10px 26px -16px rgba(26, 22, 19, .14);
  --shadow-sm: 0 1px 2px rgba(26, 22, 19, .05);

  --hover-border: #c9c5c3;
  --scrollbar-hover: #c9c5c3;
  --danger-border-solid: #dfa79e;
  --term-tag: #0f6f47;

  /* Editor row-state tints (see :root notes). */
  --row-dirty: rgba(168, 69, 31, .10);
  --row-dirty-hover: rgba(168, 69, 31, .16);
  --row-failed: rgba(187, 46, 33, .08);
  --row-failed-hover: rgba(187, 46, 33, .14);

  /* Type tokens are theme-agnostic but redefined here too (parity guard). */
  --font-body: "Hanken Grotesk", system-ui, sans-serif;
  --font-display: "Space Grotesk", system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, monospace;
  --data-font: "JetBrains Mono", ui-monospace, monospace;
  --display-tracking: -.02em;

  /* Spacing scale is theme-agnostic (layout, not color) but redefined here too
     to satisfy the light/dark token-parity guard (verify_ui_theme.py) and keep
     every :root custom property explicitly accounted for in both themes. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;
  --readable: 68ch;

  color-scheme: light;
}

/* ============================================================
   SKINS (per-user, opt-in). The skin axis is ORTHOGONAL to the light/dark theme
   axis: `data-skin` on <html> (server-rendered for the signed-in user, or the
   no-FOUC `loc_skin` bootstrap) swaps the whole TOKEN LAYER to a different visual
   identity; the theme toggle still flips dark<->light on top of it. No data-skin
   attribute == Kiln (the :root / light blocks above, untouched — a Kiln user's
   render is byte-identical to before skins existed).

   Each skin redefines EXACTLY the same token NAMES as :root (the skin-parity guard
   in verify_ui_skins.py enforces the full set), so every component that reads a
   token is reskinned with zero structural CSS changes — the type scale, spacing,
   radii semantics, shadow vocabulary and all component rules are REUSED. Only the
   values + the three font families + the display-tracking change.

   Specificity note: a single-attr skin block (e.g. html[data-skin="instrument"])
   and the html[data-theme="light"] block are equal specificity (0,1,1), so the
   COMBINED html[data-skin=...][data-theme="light"] block (0,1,2) is what wins for a
   skin user in light mode — hence all four skin*theme combinations are defined
   explicitly below, and these blocks sit AFTER the light block in source.

   Values are extracted faithfully from planning/design/direction-plates.html
   (Direction A "Precision Instrument", Direction B "The Ledger"), which were
   contrast-reviewed. A few tokens the plates predate (editor --row-* tints,
   --term-tag, --danger-border-solid, --hover-border) are derived here from each
   skin's own accent/danger/neutral hues; a couple of light accent-as-text values
   are darkened one step past the fill to clear AA on the sidebar-active composite
   (the Kiln #a8451f precedent). Every skin*theme pair is checked by
   scripts/_kiln_contrast_check.py. See DESIGN.md "Skins".
   ============================================================ */

/* ---------- Instrument (Direction A) — near-monochrome graphite, one teal
   signal. IBM Plex Sans everywhere + IBM Plex Mono for data. ---------- */
html[data-skin="instrument"] {
  --bg: #0d1013;
  --bg-2: #0f1317;
  --panel: #14181c;
  --panel-2: #1b2025;
  --elev: #1b2025;
  --border: #262d33;
  --border-soft: #1c2228;
  --text: #e8edf1;
  --text-dim: #c3ccd3;
  --muted: #94a2ad;

  --primary: #33b7aa;
  --primary-2: #4fd0c2;
  --primary-soft: rgba(51, 183, 170, .15);
  --primary-ink: #04140f;
  --ring: rgba(51, 183, 170, .45);
  --primary-border: rgba(51, 183, 170, .4);

  --success: #46c793;
  --success-soft: rgba(70, 199, 147, .15);
  --success-border: rgba(70, 199, 147, .4);
  --warn: #e0a63c;
  --warn-soft: rgba(224, 166, 60, .15);
  --warn-border: rgba(224, 166, 60, .4);
  --danger: #f0776f;
  --danger-border: rgba(240, 119, 111, .45);

  --radius: 8px;
  --radius-sm: 6px;
  --radius-xs: 4px;
  --shadow: 0 10px 28px -14px rgba(0, 0, 0, .55);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .28);

  --hover-border: #3a444c;
  --scrollbar-hover: #3a444c;
  --danger-border-solid: #6b3a36;
  --term-tag: #7fd8b0;

  --row-dirty: rgba(51, 183, 170, .16);
  --row-dirty-hover: rgba(51, 183, 170, .26);
  --row-failed: rgba(240, 119, 111, .10);
  --row-failed-hover: rgba(240, 119, 111, .18);

  --font-body: "IBM Plex Sans", system-ui, sans-serif;
  --font-display: "IBM Plex Sans", system-ui, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, monospace;
  --data-font: "IBM Plex Mono", ui-monospace, monospace;
  --display-tracking: -.01em;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;
  --readable: 68ch;

  color-scheme: dark;
}
html[data-skin="instrument"][data-theme="light"] {
  --bg: #f4f6f7;
  --bg-2: #eceef1;
  --panel: #ffffff;
  --panel-2: #eceef1;
  --elev: #ffffff;
  --border: #d9dee2;
  --border-soft: #e7eaed;
  --text: #101519;
  --text-dim: #333d45;
  --muted: #54606a;

  --primary: #0f766e;
  /* accent-as-text darkened one step past the #0f766e fill: on the sidebar-active
     composite (--primary-soft over --bg-2) #0f766e measures ~4.2:1; #0b5f58 clears
     AA there (the Kiln #a8451f precedent). The button fill stays #0f766e. */
  --primary-2: #0b5f58;
  --primary-soft: rgba(15, 118, 110, .10);
  --primary-ink: #ffffff;
  --ring: rgba(15, 118, 110, .35);
  --primary-border: rgba(15, 118, 110, .35);

  --success: #0c6f4c;
  --success-soft: rgba(12, 111, 76, .10);
  --success-border: rgba(12, 111, 76, .35);
  --warn: #7a5600;
  --warn-soft: rgba(122, 86, 0, .10);
  --warn-border: rgba(122, 86, 0, .32);
  --danger: #bd2b24;
  --danger-border: rgba(189, 43, 36, .40);

  --radius: 8px;
  --radius-sm: 6px;
  --radius-xs: 4px;
  --shadow: 0 10px 26px -16px rgba(16, 21, 25, .14);
  --shadow-sm: 0 1px 2px rgba(16, 21, 25, .05);

  --hover-border: #c2c9ce;
  --scrollbar-hover: #c2c9ce;
  --danger-border-solid: #e0a49d;
  --term-tag: #0c6f4c;

  --row-dirty: rgba(15, 118, 110, .10);
  --row-dirty-hover: rgba(15, 118, 110, .16);
  --row-failed: rgba(189, 43, 36, .08);
  --row-failed-hover: rgba(189, 43, 36, .14);

  --font-body: "IBM Plex Sans", system-ui, sans-serif;
  --font-display: "IBM Plex Sans", system-ui, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, monospace;
  --data-font: "IBM Plex Mono", ui-monospace, monospace;
  --display-tracking: -.01em;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;
  --readable: 68ch;

  color-scheme: light;
}

/* ---------- Ledger (Direction B) — instrument-of-record: paper + near-black ink,
   prussian accent, Spectral serif headings over Public Sans body/data. Its DEFAULT
   (no theme attr) is the "after dark" peer; the theme toggle brings the light paper
   home theme. Code-vs-data: --mono (the CODE face) stays the bundled JetBrains
   Mono — true monospace for code/pre.code/kbd/key readouts; --data-font carries
   the plate's "data in Public Sans lining tabular figures", with the locale-pair
   arrow (U+2192, absent upstream in Public Sans) falling back to JetBrains Mono.
   --display-tracking is ~0 (a serif must not inherit the tight negative display
   tracking). ---------- */
html[data-skin="ledger"] {
  --bg: #15171a;
  --bg-2: #181a1e;
  --panel: #1d2024;
  --panel-2: #25292e;
  --elev: #25292e;
  --border: #31363c;
  --border-soft: #23272c;
  --text: #ecebe7;
  --text-dim: #c8c9c6;
  --muted: #9aa1a8;

  --primary: #84acdb;
  --primary-2: #9dbde3;
  --primary-soft: rgba(132, 172, 219, .16);
  --primary-ink: #0b141d;
  --ring: rgba(132, 172, 219, .45);
  --primary-border: rgba(132, 172, 219, .4);

  --success: #4fbf8f;
  --success-soft: rgba(79, 191, 143, .15);
  --success-border: rgba(79, 191, 143, .4);
  --warn: #e0ad4e;
  --warn-soft: rgba(224, 173, 78, .15);
  --warn-border: rgba(224, 173, 78, .4);
  --danger: #ec7d74;
  --danger-border: rgba(236, 125, 116, .45);

  --radius: 4px;
  --radius-sm: 3px;
  --radius-xs: 2px;
  --shadow: 0 10px 28px -14px rgba(0, 0, 0, .55);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .28);

  --hover-border: #444a52;
  --scrollbar-hover: #444a52;
  --danger-border-solid: #6b3a37;
  --term-tag: #6fcea6;

  --row-dirty: rgba(132, 172, 219, .16);
  --row-dirty-hover: rgba(132, 172, 219, .26);
  --row-failed: rgba(236, 125, 116, .10);
  --row-failed-hover: rgba(236, 125, 116, .18);

  --font-body: "Public Sans", system-ui, sans-serif;
  --font-display: "Spectral", Georgia, serif;
  --mono: "JetBrains Mono", ui-monospace, monospace;
  --data-font: "Public Sans", "JetBrains Mono", ui-monospace, monospace;
  --display-tracking: 0;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;
  --readable: 68ch;

  color-scheme: dark;
}
html[data-skin="ledger"][data-theme="light"] {
  --bg: #f5f6f7;
  --bg-2: #edeff1;
  --panel: #ffffff;
  --panel-2: #edeff1;
  --elev: #ffffff;
  --border: #d7dade;
  --border-soft: #e4e6e9;
  --text: #14181c;
  --text-dim: #3a424a;
  --muted: #535a61;

  --primary: #17324d;
  --primary-2: #17324d;
  --primary-soft: rgba(23, 50, 77, .09);
  --primary-ink: #ffffff;
  --ring: rgba(23, 50, 77, .30);
  --primary-border: rgba(23, 50, 77, .30);

  --success: #0f6b45;
  --success-soft: rgba(15, 107, 69, .10);
  --success-border: rgba(15, 107, 69, .34);
  --warn: #84550a;
  --warn-soft: rgba(132, 85, 10, .10);
  --warn-border: rgba(132, 85, 10, .32);
  --danger: #b3261e;
  --danger-border: rgba(179, 38, 30, .40);

  --radius: 4px;
  --radius-sm: 3px;
  --radius-xs: 2px;
  --shadow: 0 10px 26px -16px rgba(20, 24, 28, .14);
  --shadow-sm: 0 1px 2px rgba(20, 24, 28, .05);

  --hover-border: #c3c7cb;
  --scrollbar-hover: #c3c7cb;
  --danger-border-solid: #d9a49e;
  --term-tag: #0f6b45;

  --row-dirty: rgba(23, 50, 77, .09);
  --row-dirty-hover: rgba(23, 50, 77, .15);
  --row-failed: rgba(179, 38, 30, .08);
  --row-failed-hover: rgba(179, 38, 30, .14);

  --font-body: "Public Sans", system-ui, sans-serif;
  --font-display: "Spectral", Georgia, serif;
  --mono: "JetBrains Mono", ui-monospace, monospace;
  --data-font: "Public Sans", "JetBrains Mono", ui-monospace, monospace;
  --display-tracking: 0;

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;
  --readable: 68ch;

  color-scheme: light;
}

* { box-sizing: border-box; }

html { scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); background-clip: padding-box; }
::-webkit-scrollbar-track { background: transparent; }

body {
  margin: 0;
  font-family: var(--font-body);
  /* Body role (DESIGN.md §3): 14px / 0.875rem Hanken. The app previously left
     body at the 16px UA default and hand-sized every element; this establishes
     the spec baseline so unstyled reading text and table cells land at 14px.
     rem-sized elements are unaffected (rem is root-relative), so the tuned
     per-element sizes keep their values. */
  font-size: .875rem;
  /* Kiln backgrounds are flat — the old radial-gradient wash is retired. */
  background: var(--bg);
  color: var(--text);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--primary-2); }
/* Long prose reads with fewer orphans/ragged lines (typeset guidance). Headings
   below opt into `balance` instead; short UI strings are unaffected either way. */
p, .help { text-wrap: pretty; }

/* Display font (Space Grotesk) wiring: page titles (h1 / Display), section &
   card headings (h2 / Headline), the topbar page title, and the wordmark. It is
   deliberately NOT applied to labels, buttons, table cells, or data — those stay
   Hanken / JetBrains (the Grotesk Division Rule, DESIGN.md §3). */
h1, h2, .crumbs, .brand, .brand-big, .errcode { font-family: var(--font-display); }

/* ---------------- app shell ---------------- */
.app { display: flex; min-height: 100vh; }

.sidebar {
  width: 252px; flex-shrink: 0; background: var(--bg-2);
  border-right: 1px solid var(--border-soft); padding: 14px 12px;
  /* `position: sticky` pins the sidebar on long pages AND establishes the
     absolute-positioning containing block for the .sb-resize handle (feat/
     sidebar-ux) — so the handle needs no separate `position: relative`, which
     would silently un-pin the sidebar (same specificity, later in source). */
  display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
}
.sidebar .brand {
  display: flex; align-items: center; gap: 10px; font-size: 1.08rem; font-weight: 700;
  color: var(--text); text-decoration: none; padding: 6px 8px 16px; letter-spacing: var(--display-tracking);
}
.sidebar .brand .mark {
  width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0;
  background: var(--primary); color: var(--primary-ink);
  display: grid; place-items: center; font-size: 1rem;
}
.newbtn {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--primary); color: var(--primary-ink); font-weight: 600;
  text-decoration: none; border-radius: var(--radius-sm); padding: 10px; margin-bottom: 12px;
  font-size: .9rem; box-shadow: none; transition: filter .15s, transform .05s;
}
.newbtn:hover { filter: brightness(1.07); }
.newbtn:active { transform: translateY(1px); }

.side-nav { display: flex; flex-direction: column; gap: 1px; overflow-y: auto; flex: 1; }
/* Label role (DESIGN.md §3): 12px / 0.75rem, +0.05em tracking, uppercase — the
   ONE sanctioned uppercase role. Nav-group headings live here (was 10.5px/.1em). */
.nav-group { color: var(--muted); font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; padding: 16px 10px 5px; }
.side-nav a {
  position: relative; color: var(--text-dim); text-decoration: none; padding: 8px 11px;
  border-radius: var(--radius-sm); font-size: .9rem; display: flex; align-items: center; gap: 9px;
  transition: background .12s, color .12s;
}
.side-nav a:hover { background: var(--panel); color: var(--text); }
/* Active nav: Terracotta Wash background + Clay Reading text, 600 weight — no
   side-stripe / left border (the old ::before accent stripe is retired, per
   DESIGN.md Components + the side-stripe absolute ban). */
.side-nav a.on { background: var(--primary-soft); color: var(--primary-2); font-weight: 600; }

.side-foot { border-top: 1px solid var(--border-soft); padding-top: 12px; margin-top: 8px; display: flex; align-items: center; gap: 8px; }
.prov-dots { display: flex; gap: 5px; }
.dot { width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; font-size: .66rem; font-weight: 700; }
.dot.live { background: var(--success-soft); color: var(--success); border: 1px solid var(--success-border); }
.dot.mock { background: var(--panel-2); color: var(--muted); border: 1px solid var(--border); }

/* ---------------- Customizable sidebar (feat/sidebar-ux) ----------------
   Collapse/resize/reorder. Desktop-facing: the width var, collapsed rail AND the
   .sb-controls toggles apply only >900px (mobile keeps the off-canvas overlay
   from mobilenav.js untouched — the controls are dead/harmful on touch, see
   sidebar_ux.js viewport gate). The glyph/label split lets a collapsed rail show
   icons only. RTL note (SB-11, not built): the resize handle lives on the right
   edge and the collapse chevrons assume LTR; an RTL skin would need mirroring. */
.nav-glyph { flex-shrink: 0; width: 18px; text-align: center; display: inline-block; }
.nav-section { display: flex; flex-direction: column; gap: 1px; }

/* Top controls: collapse + edit-layout toggles. Desktop-only: hidden by default
   and revealed in the min-width:901px block below (SB-04) so a touch viewport
   never shows the dead collapse toggle or the (undraggable) edit mode. */
.sb-controls { display: none; gap: 6px; justify-content: flex-end; padding: 0 2px 8px; }
.sb-controls button {
  background: transparent; border: 1px solid var(--border-soft); color: var(--text-dim);
  border-radius: var(--radius-sm); width: 28px; height: 28px; line-height: 1;
  cursor: pointer; font-size: .95rem; display: grid; place-items: center;
  transition: background .12s, color .12s, border-color .12s;
}
.sb-controls button:hover { border-color: var(--primary); color: var(--text); }
.sb-edit[aria-pressed="true"] { background: var(--primary-soft); color: var(--primary-2); border-color: var(--primary); }

/* Edit-mode toolbar (Done / Reset layout). */
.sb-edit-bar { display: none; gap: 6px; padding: 8px 2px 0; margin-top: 6px; border-top: 1px solid var(--border-soft); }
.sidebar.editing .sb-edit-bar { display: flex; }
.sb-edit-bar button {
  flex: 1; background: transparent; border: 1px solid var(--border-soft); color: var(--text-dim);
  border-radius: var(--radius-sm); padding: 7px 6px; font-size: .8rem; cursor: pointer;
  transition: filter .12s, border-color .12s;
}
.sb-edit-bar .sb-done { background: var(--primary); color: var(--primary-ink); border-color: var(--primary); font-weight: 600; }
.sb-edit-bar button:hover { filter: brightness(1.06); border-color: var(--primary); }

/* Edit-mode affordances. */
.sidebar.editing .nav-section { outline: 1px dashed var(--border); outline-offset: -2px; border-radius: var(--radius-sm); padding: 2px 0; }
.sidebar.editing .nav-group { cursor: grab; }
.sidebar.editing .side-nav a { cursor: grab; }
.sidebar.editing .nav-group::before,
.sidebar.editing .side-nav a::before { content: "⋮⋮"; margin-right: 6px; opacity: .45; letter-spacing: -2px; }
.sidebar.editing .side-nav a::before { font-size: .8rem; margin-right: 4px; }
.sb-dragging { opacity: .45; }

/* Resize handle. Sits fully INSIDE the sidebar's right edge (right:0, not the old
   right:-2px which spilled 1px over the content and showed a col-resize cursor
   there — SB-09). The 1px right border keeps it visually flush with the seam. */
.sb-resize { position: absolute; top: 0; right: 0; width: 6px; height: 100%; cursor: col-resize; z-index: 2; display: none; }
.sb-resize:hover { background: var(--primary-soft); }
body.sb-resizing { cursor: col-resize; user-select: none; }
body.sb-resizing .sidebar { transition: none; }

/* Desktop-only: custom width, the .sb-controls toggles + the collapsed rail.
   The collapsed presentation is gated on `html.sb-js` — the class sidebar_ux.js
   adds once it has actually built the expand control + label spans (SB-05). The
   <head> no-flash bootstrap pre-sets sb-js only alongside sb-collapsed, and an
   end-of-<body> guard strips sb-js if sidebar_ux.js never initialised, so a
   failed/absent script can NEVER trap the user in a control-less 56px rail. */
@media (min-width: 901px) {
  .sidebar { width: var(--sidebar-w, 252px); transition: width .16s ease; }
  .sb-controls { display: flex; }
  .sb-resize { display: block; }
  html.sb-js.sb-collapsed .sidebar { width: 56px; padding-left: 6px; padding-right: 6px; }
  html.sb-js.sb-collapsed .sb-resize { display: none; }
  html.sb-js.sb-collapsed .sb-controls { justify-content: center; }
  html.sb-js.sb-collapsed .sb-edit { display: none; }
  html.sb-js.sb-collapsed .sb-edit-bar { display: none !important; }
  html.sb-js.sb-collapsed .nav-label { display: none; }
  html.sb-js.sb-collapsed .side-nav a,
  html.sb-js.sb-collapsed .newbtn,
  html.sb-js.sb-collapsed .sidebar .brand { justify-content: center; gap: 0; }
  html.sb-js.sb-collapsed .side-nav a { padding-left: 0; padding-right: 0; }
  html.sb-js.sb-collapsed .side-nav a .badge { display: none; }
  html.sb-js.sb-collapsed .nav-group { font-size: 0; padding: 10px 4px 6px; }
  html.sb-js.sb-collapsed .nav-group::after { content: ""; display: block; border-top: 1px solid var(--border-soft); }
  html.sb-js.sb-collapsed .side-foot { justify-content: center; }
  html.sb-js.sb-collapsed .side-foot .muted { display: none; }
  html.sb-js.sb-collapsed .prov-dots { flex-wrap: wrap; justify-content: center; }
}

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* Flat opaque top surface, 1px Soft Seam bottom rule — NO backdrop blur
   (glassmorphism is banned; the bar is solid). DESIGN.md §5 Navigation. */
.topbar2 {
  display: flex; align-items: center; gap: 16px;
  padding: 13px 28px; background: var(--bg-2);
  border-bottom: 1px solid var(--border-soft); position: sticky; top: 0; z-index: 5;
}
.crumbs { font-size: 1.125rem; line-height: 1.3; font-weight: 600; letter-spacing: -.01em; }
.topsearch-form { margin-left: 8px; flex: 1; max-width: 360px; }
.topsearch {
  width: 100%; background: var(--panel); border: 1px solid var(--border);
  border-radius: 999px; color: var(--text); padding: 8px 14px; font-size: .85rem;
}
.topsearch::placeholder { color: var(--muted); }
.topsearch:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--ring); }
.topactions { display: flex; gap: 10px; align-items: center; margin-left: auto; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--elev); color: var(--primary-2); display: grid; place-items: center; font-size: .8rem; font-weight: 700; border: 1px solid var(--border); }
.btn-primary {
  background: var(--primary); color: var(--primary-ink); text-decoration: none;
  font-weight: 600; border-radius: var(--radius-sm); padding: 8px 15px; font-size: .88rem;
  box-shadow: none; transition: filter .15s, transform .05s;
}
.btn-primary:hover { filter: brightness(1.07); }
.btn-primary:active { transform: translateY(1px); }
/* Full-width, viewport-responsive: content fills .main (viewport minus the fixed
   sidebar) at any width, so an ultrawide screen is used edge-to-edge instead of
   capping at a fixed column. (Per-page reading widths like .docs-body / .narrow keep
   their own intentional caps.) */
.content { padding: 26px 28px 48px; width: 100%; }

.hero { margin-bottom: var(--space-5); }
.hero h1 { margin: 0 0 6px; }
/* Type scale (DESIGN.md §3, ratio ~1.2). Display=h1 (25px), Headline=h2 (18px),
   h3 a Hanken sub-heading (16px) — Space Grotesk stays on h1/h2 only (Grotesk
   Division Rule). text-wrap: balance evens heading line lengths. */
h1 { font-size: 1.5625rem; font-weight: 600; line-height: 1.2; letter-spacing: var(--display-tracking); text-wrap: balance; }
h2 { font-size: 1.125rem; font-weight: 600; line-height: 1.3; letter-spacing: -.01em; text-wrap: balance; }
h3 { font-size: 1rem; line-height: 1.35; font-weight: 600; letter-spacing: -.005em; text-wrap: balance; }
.muted { color: var(--muted); }
.small { font-size: .82rem; }
.mt { margin-top: var(--space-5); }

/* Standard helper-paragraph style (copy/density audit, wave 1): replaces the old
   `.muted.small` convention for standalone helper text under a heading/control.
   One step higher-contrast than .muted (--text-dim vs --muted) since tiny+low-
   contrast text was the readability complaint; capped at --readable so it never
   runs the full width of the ultrawide .content column. */
.help { color: var(--text-dim); font-size: .85rem; max-width: var(--readable); margin: var(--space-1) 0 0; text-wrap: pretty; }
/* A few former `.muted.small.mt` sites wanted the LARGER .mt gap (separation from
   a preceding block), not .help's default tight 4px caption spacing. Two-class
   selector beats source order so this wins regardless of declaration position. */
.help.mt { margin-top: var(--space-5); }

/* Visually-hidden but screen-reader-accessible label (a11y). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Skip-to-content link (a11y, SB-08): the first focusable element on the page,
   off-screen until focused, so a keyboard user can jump the sidebar (incl. the
   injected collapse/edit controls) instead of tabbing through every nav item. */
.skip-link {
  position: absolute; left: 8px; top: -48px; z-index: 100;
  background: var(--primary); color: var(--primary-ink); text-decoration: none;
  padding: 8px 14px; border-radius: var(--radius-sm); font-size: .88rem; font-weight: 600;
  transition: top .12s ease;
}
.skip-link:focus { top: 8px; outline: none; box-shadow: 0 0 0 3px var(--ring); }

/* ---------------- KPI cards ---------------- */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; margin-bottom: 20px; }
.kpi {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 18px; box-shadow: var(--shadow-sm);
  transition: transform .12s, border-color .12s;
}
.kpi:hover { transform: translateY(-2px); border-color: var(--hover-border); }
.kpi-num { display: block; font-size: 1.95rem; font-weight: 750; color: var(--text); line-height: 1.1; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.kpi-lbl { color: var(--muted); font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; }

/* progress + activity */
.prog { background: var(--panel-2); border-radius: 999px; height: 7px; overflow: hidden; width: 130px; }
.prog-bar { background: var(--primary); height: 100%; border-radius: 999px; }
.activity { list-style: none; padding: 0; margin: 0; }
.activity li { padding: 9px 0; border-bottom: 1px solid var(--border-soft); display: flex; align-items: center; gap: 8px; }
.activity li:last-child { border-bottom: none; }
.activity a { color: var(--primary-2); text-decoration: none; font-weight: 500; }
.quick { display: flex; flex-wrap: wrap; gap: 8px; }

/* ---------------- tiles ---------------- */
.tilegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 14px; }
.tile { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 15px; display: flex; flex-direction: column; gap: 5px; transition: transform .12s, border-color .12s; }
.tile:hover { transform: translateY(-2px); border-color: var(--hover-border); }
.tile-top { display: flex; align-items: center; justify-content: space-between; }
.tile-icon { font-size: 1.5rem; }
.tile-link { text-decoration: none; color: inherit; }
.tile-link:hover { border-color: var(--primary); }
.tile-foot { display: flex; align-items: center; justify-content: space-between; margin-top: auto; padding-top: 8px; }
.danger-btn, button.danger, .btn-danger { color: var(--danger) !important; border-color: var(--danger-border-solid) !important; }
.danger-btn:hover, button.danger:hover, .btn-danger:hover { background: rgba(240, 121, 107, .10) !important; }
.sync-cta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.expand { cursor: pointer; }
.muted-badge { background: var(--panel-2); color: var(--muted); border: 1px solid var(--border); }
.testout { min-height: 1em; }
.preset-card { margin: 10px 0; }

/* ---------------- reports bars ---------------- */
.barrow { display: flex; align-items: center; gap: 10px; margin: 7px 0; }
/* Label role (DESIGN.md §3: "table headers, control-module names, ... stat
   labels" — same treatment as .kpi-lbl / .table thead th): uppercase, tracked,
   Ash on the surface. Wave-3 polish; .barlbl only appears in the reports.html
   bars macro, so this is not a new selector, just filling in the existing one. */
.barlbl { width: 150px; font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); text-align: right; }
.bar { flex: 1; background: var(--panel-2); border-radius: 6px; height: 18px; overflow: hidden; }
.bar-fill { background: var(--primary); height: 100%; border-radius: 6px; }
.barval { width: 40px; font-size: .85rem; }

/* ---------------- layout helpers ---------------- */
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin: 0 0 18px; }

/* ---------------- mobile nav (hamburger) ---------------- */
/* Bug #14: below 900px the sidebar used to just vanish (display:none) with no
   way back in. The hamburger toggle (topbar2, JS-driven via mobilenav.js) slides
   the sidebar in as an overlay instead of hiding it outright; a backdrop click or
   Escape closes it. Without JS the button is inert but the page still renders -
   the sidebar just stays off-canvas until the viewport widens past 900px. */
.hamburger {
  display: none; background: transparent; border: 1px solid var(--border);
  border-radius: var(--radius-sm); color: var(--text); width: 36px; height: 36px;
  align-items: center; justify-content: center; font-size: 1.1rem; padding: 0;
  box-shadow: none; cursor: pointer;
}
.hamburger:hover { border-color: var(--primary); filter: none; }
.nav-backdrop {
  display: none; position: fixed; inset: 0; background: rgba(5, 8, 14, .6);
  z-index: 20;
}
@media (max-width: 900px) {
  .grid2 { grid-template-columns: 1fr; }
  .hamburger { display: flex; }
  .sidebar {
    position: fixed; left: 0; top: 0; z-index: 30; transform: translateX(-100%);
    transition: transform .18s ease; box-shadow: var(--shadow);
  }
  .app.nav-open .sidebar { transform: translateX(0); }
  .app.nav-open .nav-backdrop { display: block; }
}

.card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-5); margin-bottom: var(--space-5); box-shadow: var(--shadow-sm); }
.card h2 { margin-top: 0; }

/* Reading cap (copy/density audit, wave 1): prose and labels inside a card stay
   legible on the ultrawide .content column. Deliberately narrow to elements, not
   the card itself — tables, .prog, pre/code, the SVG graph, fact-strips, and chip
   rows all need their natural width and are untouched by this selector. Short
   single-line inputs nested in a capped label inherit the cap (fine — they're
   short-answer controls). */
.card p, .card .help, .card label { max-width: var(--readable); }
/* ...but a label wrapping a MULTI-LINE control (prompt editors, project/workflow/
   step "describe" briefs, the Jira JSON box, engine custom-instructions) must keep
   the card's full width — the reading cap is for prose you read, not text you
   author. :has() is Baseline-2023 (all evergreen browsers); this tool ships to
   modern browsers only. Replaces the earlier .wide opt-out, which was never applied
   to any template so every authoring textarea shipped wrongly capped at 68ch. */
.card label:has(textarea) { max-width: none; }

/* ---------------- forms ---------------- */
.form { display: flex; flex-direction: column; gap: var(--space-4); }
.form label { display: flex; flex-direction: column; gap: 5px; font-size: .875rem; color: var(--muted); font-weight: 500; }
/* Text-control theming is GLOBAL (not scoped to .form): any input/select/textarea
   anywhere renders dark-themed instead of browser-default white. Control types that
   must keep native rendering (checkbox/radio/file/range/color) are excluded via :not
   so padding/background don't distort them. Declarations are unchanged from the old
   `.form input, .form select, textarea` rule — only the scope widened, so existing
   .form controls look identical. See the "specificity guard" block at end of file
   for the two pre-existing controls (.topsearch, .report-fields) that carried their
   own look and need to win against this now-wider selector. */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]), select, textarea {
  background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
  color: var(--text); padding: 10px 12px; font-size: .92rem; font-family: inherit; transition: border-color .12s, box-shadow .12s;
}
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--ring); }
textarea { resize: vertical; }

button {
  align-self: flex-start; background: var(--primary); color: var(--primary-ink);
  border: none; border-radius: var(--radius-sm); padding: 10px 17px; font-weight: 600; font-size: .9rem;
  cursor: pointer; box-shadow: none; transition: filter .15s, transform .05s;
}
button:hover { filter: brightness(1.07); }
button:active { transform: translateY(1px); }
button.small, .btn-small { padding: 6px 12px; font-size: .82rem; box-shadow: none; }
button.ghost, button.btn-secondary { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); box-shadow: none; }
button.ghost:hover, button.btn-secondary:hover { border-color: var(--primary); filter: none; }
button:disabled { opacity: .5; cursor: default; filter: none; transform: none; }

.list { padding-left: 18px; }
.list li { margin: 4px 0; }

/* ---------------- tables (Tabular Ledger, DESIGN.md §3 + §5) ----------------
   Ruled + dense + calm: uppercase Label headers on a Seam header rule, 1px Soft
   Seam row rules, neutral-step row hover, no zebra. tabular-nums on the whole
   table so every numeric column aligns in fixed-width figures (proportional
   figures in a data column are a defect). `.grid` (tm/glossary/templates/segment
   lists) now shares the SAME Tabular-Ledger chrome as `.table` — header rule,
   Soft-Seam row rules, neutral-step hover, 550-weight links — via the shared
   selectors below (wave 2). `.grid`'s per-column width semantics (`.grid td.tm/
   .terms/.num/.src/.tgt`, `.editor td.*`) and the editor's `--row-*` state tints
   are declared later in source at >= specificity, so they still win intact; the
   `table grid` combos (document/editor/lqa) already inherited this from `.table`
   in wave 1, so only standalone `.grid` lists change. */
.table, .grid { font-variant-numeric: tabular-nums; }
.table, .grid { width: 100%; border-collapse: collapse; }
.table th, .table td, .grid th, .grid td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--border-soft); }
/* Label role (12px/0.75rem) + a stronger Seam header rule above the softer row rules. */
.table thead th, .grid thead th { color: var(--muted); font-weight: 600; font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; border-bottom: 1px solid var(--border); }
.table tbody tr, .grid tbody tr { transition: background .1s; }
.table tbody tr:hover, .grid tbody tr:hover { background: var(--bg-2); }
.table a, .grid a { color: var(--primary-2); text-decoration: none; font-weight: 550; }
.table a:hover, .grid a:hover { text-decoration: underline; }
/* Data role: mono + tabular figures for data-shaped cells (locale codes like
   en-US → de-DE, counts, costs, hashes, IDs, timestamps). Applied surgically in
   templates. Deliberately unsized so it inherits the cell's size and stays
   vertically aligned with its neighbours (JetBrains Mono runs a touch large). */
.data-mono { font-family: var(--data-font); font-variant-numeric: tabular-nums; }

.chips { list-style: none; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.chip { background: var(--panel-2); border: 1px solid var(--border); border-radius: 999px; padding: 3px 11px; font-size: .8rem; color: var(--text-dim); }
.chip-x { margin-left: 6px; color: var(--muted); text-decoration: none; font-weight: 700; }
.chip-x:hover { color: var(--danger, #c0392b); }

.back { color: var(--primary-2); text-decoration: none; font-size: .88rem; }
.next .roadmap { color: var(--muted); }
.next .roadmap li { margin: 4px 0; }
/* .88em scales with context but must never dip under the 12px floor (inside
   .small/.help it would land ~11.5px) — max() pins it at .75rem. */
code { background: var(--panel-2); padding: 1px 6px; border-radius: var(--radius-xs); font-size: max(.88em, .75rem); font-family: var(--mono, monospace); }

.error { color: var(--danger); background: rgba(240,121,107,.08); border: 1px solid var(--danger-border-solid); border-radius: var(--radius-sm); padding: 9px 12px; }
.error-inline { color: var(--danger); }
.ok { color: var(--success); }

/* upload modes */
.modes { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 11px 13px; }
.modes legend { color: var(--muted); padding: 0 6px; font-size: .82rem; }
.radio { flex-direction: row !important; align-items: flex-start; gap: 8px; color: var(--text) !important; }
.radio input { width: auto; margin-top: 3px; }
.radio span { font-size: .9rem; }

/* tabs */
.tabs { display: flex; gap: 6px; margin-top: 12px; flex-wrap: wrap; }
.tab { padding: 6px 13px; border: 1px solid var(--border); border-radius: 999px; color: var(--muted); text-decoration: none; font-size: .84rem; transition: all .12s; }
.tab:hover { color: var(--text); border-color: var(--hover-border); }
.tab.active { background: var(--primary-soft); color: var(--primary-2); border-color: var(--primary); font-weight: 600; }

/* Status badges — "Signal dots" (Option A, planning/design/status-plates.html,
   chosen by Brent). The dot + uppercase-micro-label treatment is STATUS-ONLY
   (the plate's vocabulary is explicitly status): it applies to a `.badge` that
   carries a state modifier (.run/.ok/.warn/.danger/.exact/.fuzzy/.semantic/
   .reauth/.paused/.beta) OR the explicit neutral-status marker `.status`. A BARE
   `.badge` with none of those stays the quiet neutral "Raised Clay" pill below —
   because `.badge` is also used for config / data / name / type TAGS (assignee
   email, customer name, "adaptive feedback (n=3)", cache tokens, run/batch type)
   that must NOT be case-folded into a status dot. Each state maps to an EXISTING
   token via two component-scoped custom props, --sig (dot) and --sig-ink (label);
   the shared dot/pulse/hollow/squared geometry, the narrow collapse, and the
   reduced-motion degrade live in the "Signal-dot machinery" section further down.
   Live states (.run = machine working, .reauth = reconnect) pulse; Beta+Semantic
   use a HOLLOW dot; Paused a SQUARED dot — non-colour redundancy. Every state
   selector sits at (0,2,0), above this (0,1,0) base, so it wins regardless of
   source order (the base pill only paints when no state/`.status` is present). */
.badge { font-size: .75rem; font-weight: 600; padding: 3px 9px; border-radius: 999px; display: inline-block; background: var(--panel-2); color: var(--muted); border: 1px solid var(--border); }
.badge.status { --sig: var(--muted); --sig-ink: var(--muted); }
.badge.warn { --sig: var(--warn); --sig-ink: var(--warn); }
.badge.reauth { --sig: var(--danger); --sig-ink: var(--danger); }
.badge.exact { --sig: var(--success); --sig-ink: var(--success); }
.badge.fuzzy { --sig: var(--warn); --sig-ink: var(--warn); }
.badge.semantic { --sig: var(--primary-2); --sig-ink: var(--primary-2); }
.badge.beta { --sig: var(--primary-2); --sig-ink: var(--primary-2); }
.badge.paused { --sig: var(--warn); --sig-ink: var(--warn); }

.inline-form { flex-direction: row !important; flex-wrap: wrap; align-items: flex-end; gap: 13px; }
.inline-form label { flex-direction: column; }
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.row-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.justfilled { animation: flashfill 1s ease-out; }
@keyframes flashfill { from { background: var(--primary-soft); } to { background: transparent; } }
.suggest { color: var(--success); font-style: italic; opacity: .85; }

.btnlink { display: inline-block; background: var(--panel-2); border: 1px solid var(--border); color: var(--text); text-decoration: none; border-radius: var(--radius-sm); padding: 7px 13px; font-size: .85rem; transition: border-color .12s; }
.btnlink:hover { border-color: var(--primary); }

/* segment / term grid */
.grid td.tm { width: 60px; }
.grid td.terms { width: 22%; }
.grid td.num { color: var(--muted); width: 38px; text-align: right; font-family: var(--data-font); font-variant-numeric: tabular-nums; }
.grid td.src { width: 44%; }
.grid td.tgt { width: 36%; }
.grid td { vertical-align: top; }
.grid .tag { background: var(--primary-soft); color: var(--primary-2); margin-right: 3px; }
.term { display: inline-block; background: var(--success-soft); border: 1px solid var(--success-border); color: var(--term-tag); border-radius: var(--radius-xs); padding: 1px 7px; margin: 1px 2px; font-size: .8rem; }
.term.dnt { background: var(--warn-soft); border-color: var(--warn-border); color: var(--warn); }
.term em { color: var(--muted); font-style: normal; }

/* inline-fidelity tag chips (read-only surfaces) — de-noised stand-in for the
   ⟦r1⟧/⟦g1⟧/⟦x1⟧ reconstruction tokens stored in segment text. */
.tag-chip { display: inline-block; min-width: 1.1em; text-align: center; padding: 0 5px;
  margin: 0 1px; border-radius: var(--radius-xs); font-size: .72rem; font-weight: 600; line-height: 1.5;
  vertical-align: baseline; user-select: none; cursor: default;
  background: var(--primary-soft); color: var(--primary-2); border: 1px solid var(--primary-border); }
.tag-chip.open { border-top-right-radius: 2px; border-bottom-right-radius: 2px; }
.tag-chip.open::before { content: "◃"; opacity: .6; margin-right: 2px; }
.tag-chip.close { border-top-left-radius: 2px; border-bottom-left-radius: 2px; }
.tag-chip.close::after { content: "▹"; opacity: .6; margin-left: 2px; }
.tag-chip.point { background: var(--panel-2); color: var(--text-dim); border-color: var(--border); }
.tag-hint { display: inline-block; margin: 0 0 4px; padding: 1px 7px; border-radius: var(--radius-xs);
  background: var(--warn-soft); border: 1px solid var(--warn-border); color: var(--warn); }
.tag-hint code { background: transparent; color: inherit; padding: 0; }

/* workflow builder + runs */
.steplist { padding-left: 18px; }
.step, .runstep { background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 11px 13px; margin: 8px 0; }
.step-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.inline-del { margin-left: auto; }
.prompt { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 9px 11px; white-space: pre-wrap; font-size: .82rem; color: var(--text-dim); overflow-x: auto; margin: 8px 0 0; }
.preset { color: var(--primary-2); text-decoration: none; }

/* CAT editor */
.editor td { vertical-align: top; }
.tgt-edit { width: 100%; min-width: 220px; }
.mini { padding: 3px 9px; font-size: .75rem; border-radius: var(--radius-xs); background: var(--panel-2); color: var(--text); border: 1px solid var(--border); cursor: pointer; margin: 1px; box-shadow: none; }
.mini:hover { border-color: var(--primary); filter: none; }
.mini.ok-btn { color: var(--success); }
.acts { white-space: nowrap; position: relative; }
.altmenu { position: absolute; right: 0; z-index: 10; background: var(--elev); border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow); min-width: 240px; }
.altitem { padding: 9px 11px; cursor: pointer; border-bottom: 1px solid var(--border-soft); font-size: .85rem; }
.altitem:hover { background: var(--panel-2); }
.concrow { padding: 5px 0; border-bottom: 1px solid var(--border-soft); font-size: .85rem; }
kbd { background: var(--panel-2); border: 1px solid var(--border); border-bottom-width: 2px; border-radius: var(--radius-xs); padding: 0 5px; font-size: .8rem; font-family: var(--mono, monospace); }

/* LQA */
.bigscore { font-size: 1.7rem; font-weight: 750; }
.bigscore.good { color: var(--success); }
.bigscore.bad { color: var(--danger); }
.lqaerr { margin: 3px 0; font-size: .85rem; }

/* --- Help / docs --- */
.docs { display: grid; grid-template-columns: 190px 1fr; gap: 32px; align-items: start; }
@media (max-width: 900px) { .docs { grid-template-columns: 1fr; } .docs-toc { display: none; } }
.docs-toc { position: sticky; top: 80px; }
.toc-title { color: var(--muted); font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; margin-bottom: 8px; }
/* Punch-list: the old 2px transparent border-left that filled to a colored
   stripe on hover was a side-stripe affordance (the ban). Replaced with a
   neutral-step background tint + rounded corners — the nav hover pattern. */
.docs-toc a { display: block; color: var(--muted); text-decoration: none; padding: 4px 8px; border-radius: var(--radius-xs); font-size: .85rem; transition: background .12s, color .12s; }
.docs-toc a:hover { color: var(--text); background: var(--panel-2); }
/* Scroll-spy active state (tocspy.js): the current section's link takes the
   side-nav active look — Terracotta Wash background + Clay Reading text, 600
   weight, NO side-stripe. Purely a JS-driven `.on` toggle; with JS off the TOC
   is hover-only exactly as before. */
.docs-toc a.on { background: var(--primary-soft); color: var(--primary-2); font-weight: 600; }
.docs-body { max-width: 760px; }
.doc-section { margin-bottom: 34px; scroll-margin-top: 80px; }
.doc-section h1 { margin-top: 0; }
.doc-section h2 { border-bottom: 1px solid var(--border-soft); padding-bottom: 8px; margin-bottom: 12px; }
.doc-list { padding-left: 20px; }
.doc-list li { margin: 7px 0; }
pre.code { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 13px 15px; overflow-x: auto; font-size: .84rem; line-height: 1.5; color: var(--text-dim); white-space: pre; font-family: var(--mono, monospace); }
.table.api td { vertical-align: middle; }
/* Literal-caps chips (GET/POST) join the Label/badge scale: 12px floor, +0.05em
   caps tracking (was 10.9px — literal capitals dodge a text-transform grep). */
.method { font-size: .75rem; font-weight: 700; padding: 2px 7px; border-radius: var(--radius-xs); letter-spacing: .05em; }
.method.get { background: var(--success-soft); color: var(--success); border: 1px solid var(--success-border); }
.method.post { background: var(--primary-soft); color: var(--primary-2); border: 1px solid var(--primary-border); }
/* The keys/Delta group is the first to use verbs beyond GET/POST. PUT and PATCH
   read as writes (the POST treatment); DELETE gets the danger palette, because a
   reader scanning a table of forty rows should be able to spot the destructive one
   without reading it. */
.method.put, .method.patch { background: var(--primary-soft); color: var(--primary-2); border: 1px solid var(--primary-border); }
.method.delete { background: var(--bg-2); color: var(--danger); border: 1px solid var(--danger-border); }

/* --- key grid: two-column shell (rail + keys) ---
   Follows the .docs / .docs-toc rail+content pattern: a narrow left rail
   (Filter/Translate/Add-a-key/Export/Import/Key-settings/Ask) sticky beside
   a scrolling right column of key cards. Collapses to one column, rail on
   top, at the same 900px breakpoint as .grid2/.docs. The rail itself can run
   taller than the viewport (many cards, most gated open) — overflow-y:auto +
   a max-height keep it from being clipped or pushing the sticky position off
   past the viewport bottom. */
.keys-layout { display: grid; grid-template-columns: 280px 1fr; gap: var(--space-5); align-items: start; }
.keys-rail {
  position: sticky; top: 80px; max-height: calc(100vh - 100px);
  overflow-y: auto; overflow-x: hidden; padding-right: 2px;
}
.keys-main { min-width: 0; }
@media (max-width: 900px) {
  .keys-layout { grid-template-columns: 1fr; }
  .keys-rail { position: static; max-height: none; overflow: visible; top: auto; }
}

/* Per-key cards replacing the old matrix table (issue #48). Each key is one
   compact unit: name/badges, the source line, one block per target locale,
   then actions — dense stacking for the 100+-key pages these pages usually
   are. `.plural-line` is the #48 fix: every plural category (base AND each
   target locale) gets its own block-level line instead of flowing as inline
   `.rowform` siblings in one cell — `.rowform` itself stays display:inline
   (style.css above), unchanged for the 7 other templates that rely on it. */
.keycards { display: flex; flex-direction: column; gap: 10px; margin: 10px 0 22px; }
.keycard { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--panel-2); padding: var(--space-3) var(--space-4); }
.keycard-head { margin-bottom: 6px; }
.keycard-head .muted.small { margin-top: 2px; }
.keycard-locale { padding: 7px 0; border-top: 1px solid var(--border-soft); }
.keycard-source { border-top: none; padding-top: 2px; }
.loc-label { font-weight: 600; letter-spacing: .04em; font-size: .72rem; margin-bottom: 4px; display: flex; align-items: center; gap: 6px; }
.plural-values { display: flex; flex-direction: column; gap: 6px; }
.plural-line { display: block; overflow-wrap: anywhere; }
.plural-line .rowform { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.plural-line .rowform input[type="text"] { flex: 1 1 auto; min-width: 120px; width: auto; }
.keycard-actions { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border-soft); }
.keycard-actions .form label { max-width: none; }

/* Live progress for the runs a Translate just launched (issue #47), at the top
   of the keys column. One row per run; a FAILED run's step error is rendered
   below its row (issue #44's silent-failure surface) and stays put -- the
   widget deliberately does NOT auto-reload past a failure. */
.runstrip {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--panel-2); padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}
.runstrip-title { margin: 0 0 8px; font-size: .9rem; }
.runstrip-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 5px 0; border-top: 1px solid var(--border-soft);
}
.runstrip-row:first-of-type { border-top: none; }
.runstrip-locale { font-weight: 600; min-width: 68px; }
.runstrip-state { font-size: .78rem; letter-spacing: .03em; }
.runstrip-row[data-run-status="failed"] .runstrip-state { color: var(--danger); font-weight: 600; }
.runstrip-error { flex: 1 1 100%; font-size: .8rem; overflow-wrap: anywhere; }
.runstrip-error[hidden] { display: none; }
.runstrip-foot { margin: 8px 0 0; }

/* F9: Export / Import / Key settings collapse behind a default-closed
   <details> in the rail (project_keys.html) -- without it the rail can run
   ~2500-3000px tall with everything open, burying Export/Import/Key settings/
   Ask past a max-height scroll container. Filter/Translate/Add a Key stay
   always-open. These already sit inside a `.card` box, so the chrome is just
   the summary + a disclosure arrow, not another nested box; the summary
   wraps the untouched `<h2>` so it keeps the exact card-heading look. */
.rail-collapse { margin: 0; }
.rail-collapse > summary { list-style: none; cursor: pointer; }
.rail-collapse > summary::-webkit-details-marker { display: none; }
.rail-collapse > summary::before { content: "▸"; display: inline-block; width: 1em; color: var(--muted); font-size: .8em; transition: transform .12s; }
.rail-collapse[open] > summary::before { transform: rotate(90deg); }
.rail-collapse > summary h2 { display: inline; }
.rail-collapse > *:not(summary) { margin-top: var(--space-3); }

/* --- auth pages --- */
.authbody { display: grid; place-items: center; min-height: 100vh; padding: 24px;
  background: var(--bg); }
.authcard { width: 100%; max-width: 380px; background: var(--panel); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 28px; box-shadow: var(--shadow); }
/* Auth-screen wordmark = Display role (DESIGN.md §3: "page titles and the
   auth-screen wordmark"): 25px/600 Space Grotesk, −0.02em. */
.brand-big { display: flex; align-items: center; gap: 10px; font-size: 1.5625rem; font-weight: 600; letter-spacing: var(--display-tracking); line-height: 1.2; margin-bottom: 6px; }
.brand-big .mark { width: 32px; height: 32px; border-radius: 9px; background: var(--primary); color: var(--primary-ink); display: grid; place-items: center; }

/* toggles */
.toggle { display: flex !important; flex-direction: row !important; align-items: flex-start; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--border-soft); color: var(--text) !important; }
.toggle:last-child { border-bottom: none; }
.toggle input { width: 18px; height: 18px; margin-top: 2px; accent-color: var(--primary); flex-shrink: 0; }
.toggle span { font-size: .9rem; font-weight: 400; }
.rowform { display: inline; }
.rowform select { background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-xs); color: var(--text); padding: 3px 6px; font-size: .82rem; }
.linkbox { width: 100%; min-width: 280px; background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-xs); color: var(--primary-2); padding: 5px 8px; font-size: .78rem; font-family: var(--mono, monospace); }
.sep { border: none; border-top: 1px solid var(--border-soft); margin: 16px 0; }

/* per-project mini stats */
.stats { display: flex; flex-wrap: wrap; gap: 14px; }
.stat { background: var(--panel-2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px 18px; min-width: 92px; text-align: center; }
.statnum { display: block; font-size: 1.5rem; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.statlbl { color: var(--muted); font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; }

/* ---- profile dropdown (topbar) ---- */
.usermenu { position: relative; }
.usermenu > summary {
  display: flex; align-items: center; gap: 8px; cursor: pointer; list-style: none;
  padding: 4px 8px 4px 4px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--panel-2); transition: border-color .12s, background .12s; user-select: none;
}
.usermenu > summary::-webkit-details-marker { display: none; }
.usermenu > summary:hover { border-color: var(--primary); }
.usermenu[open] > summary { border-color: var(--primary); background: var(--panel); }
.um-id { display: flex; flex-direction: column; line-height: 1.15; max-width: 160px; }
.um-name { font-size: .82rem; color: var(--text); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Role is a VALUE, not a label — a tracked-uppercase micro-caption on a value is
   the eyebrow tell, and DESIGN.md §3 sanctions uppercase only for the Label role
   (headers / group headings / stat labels). Demoted to normal-case Ash. */
.um-role { font-size: .72rem; color: var(--muted); }
.um-caret { color: var(--muted); font-size: .7rem; transition: transform .12s; }
.usermenu[open] .um-caret { transform: rotate(180deg); }
.um-panel {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 50; min-width: 230px;
  background: var(--elev); border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--shadow); padding: 6px; animation: um-pop .12s ease;
}
@keyframes um-pop { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.um-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--border-soft); margin-bottom: 6px; }
.um-head .um-name { white-space: normal; }
.um-panel a {
  display: block; padding: 8px 10px; border-radius: var(--radius-xs); color: var(--text-dim);
  text-decoration: none; font-size: .86rem; transition: background .12s, color .12s;
}
.um-panel a:hover { background: var(--panel-2); color: var(--text); }
.um-sep { border-top: 1px solid var(--border-soft); margin: 6px 0; }
.um-danger { color: var(--danger) !important; }
.um-danger:hover { background: rgba(240, 121, 107, .10) !important; }

/* ---- dashboard ---- */
.dash-head h1 { margin: 0 0 4px; }
/* Dashboard summary is the landing screen's glanceable status line (Principle 1:
   control made visible) — body size (not an off-scale .92rem) and readable dim
   ink, not muted-tiny; the .accent/.warn-text spans inside still carry the
   in-progress / awaiting-review emphasis. */
.dash-summary { font-size: .875rem; color: var(--text-dim); }
.accent { color: var(--primary-2); }
.warn-text { color: var(--warn); }
.projects-table .proj-name { font-weight: 600; color: var(--text); text-decoration: none; }
.projects-table .proj-name:hover { color: var(--primary-2); }
.projects-table td { vertical-align: middle; }
.prog-cell { min-width: 150px; }

/* ---- app-wide warning banner (e.g. mock MT fallback) ---- */
.warnbar {
  margin: 12px 24px 0; padding: 10px 14px; border-radius: var(--radius-sm);
  background: rgba(230, 180, 85, .10); border: 1px solid rgba(230, 180, 85, .45);
  color: var(--warn, #e6b455); font-size: .9rem;
}
.warnbar a { color: inherit; font-weight: 600; }
/* spend-cap kill switch tripped: vermilion, not amber (harder stop than the mock
   warning) — the Clay-Is-Not-Red rule keeps this out of the terracotta accent. */
.warnbar.spendbar {
  background: rgba(240, 121, 107, .12); border-color: rgba(240, 121, 107, .55);
  color: var(--danger, #f0796b);
}

/* ---- error page ---- */
.errcode { font-size: 2.4rem; margin: 10px 0 6px; color: var(--text); }

/* ---- pagination ---- */
.pager { display: flex; gap: 10px; align-items: center; margin-top: 14px; font-size: .9rem; }
.pager a { color: var(--primary-2); text-decoration: none; }
.pager a:hover { text-decoration: underline; }
.pager .muted { margin: 0 4px; }

/* ---- background-job progress ---- */
.job-watch { margin: 10px 0 4px; max-width: 480px; }
.job-watch .prog { margin-bottom: 4px; }
.job-watch.job-failed .job-label { color: var(--danger); }

/* green status badge (Security page: OIDC ready) */
.badge.ok { --sig: var(--success); --sig-ink: var(--success); }

/* --- language picker (P2, item 1) --- */
.lp-fieldset { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px 14px; margin: 10px 0; }
.lp-fieldset legend { padding: 0 6px; font-weight: 600; font-size: .9rem; }
.langpicker { margin-top: 6px; }
.lp-controls { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 8px; }
.lp-search { flex: 1 1 200px; min-width: 160px; }
.lp-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.lp-chip { display: inline-flex; align-items: center; gap: 2px; }
.lp-chip-x { background: none; border: none; color: var(--text-dim); cursor: pointer; font-size: 1rem; line-height: 1; padding: 0 2px; box-shadow: none; }
.lp-chip-x:hover { color: var(--danger); }
.lp-list { max-height: 260px; overflow-y: auto; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px; background: var(--panel-2);
  /* Compact multi-column checklist (Phrase/Lokalise convention): each option is
     one short row, flowing into as many columns as fit. */
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 2px 10px; align-content: start; }
.lp-list.lp-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 2px 12px; max-height: 420px; }
/* `.form label` (column layout for regular form fields) outspecifies `.lp-opt`
   and was stacking each checkbox ABOVE its centered name — the exact anti-
   pattern. Re-assert the row layout at equal-or-higher specificity. */
.lp-opt, .form label.lp-opt, .inline-form label.lp-opt {
  display: flex; flex-direction: row; align-items: center; gap: 8px;
  padding: 3px 6px; border-radius: var(--radius-sm); cursor: pointer;
  font-weight: 400; text-align: left; min-width: 0; }
.lp-opt:hover { background: var(--panel); }
.lp-opt input { margin: 0; flex: 0 0 auto; }
.lp-name { flex: 1; color: var(--text); font-size: .85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-code { color: var(--text-dim); font-size: .78rem; font-family: var(--data-font, monospace); }

/* --- New-project popout (R2 item 2) --------------------------------------- */
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.card-head h2 { margin: 0; }
dialog.modal { border: none; border-radius: var(--radius, 10px); padding: 0; background: transparent;
  width: min(760px, calc(100vw - 32px)); max-height: calc(100vh - 48px); }
dialog.modal::backdrop { background: rgba(0, 0, 0, .55); }
dialog.modal .modal-card { margin: 0; max-height: calc(100vh - 48px); overflow-y: auto; }
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.modal-head h2 { margin: 0; }

/* --- Report builder (R2-1) --- */
.report-form { margin-top: 12px; }
.report-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.report-fields label { display: flex; flex-direction: column; gap: 4px; font-size: .82rem; color: var(--text-dim); }
.report-fields select, .report-fields input { padding: 6px 8px; }
.report-actions { margin-top: 14px; }
.report-results { margin-top: 18px; }
.report-results-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 8px; }
.tablewrap { overflow-x: auto; }
.report-table { width: 100%; border-collapse: collapse; font-size: .85rem; font-variant-numeric: tabular-nums; }
.report-table th, .report-table td { border: 1px solid var(--border); padding: 6px 9px; text-align: right; white-space: nowrap; }
.report-table th:first-child, .report-table td:first-child { text-align: left; }
.report-table thead th { background: var(--bg-2); }
.report-table tfoot td { background: var(--bg-2); font-weight: 600; }

/* --- per-pair provider matrix (Phase 2) --- */
label.inline { display: block; margin: 4px 0; font-weight: normal; }
.pp-matrix { border-collapse: collapse; margin: 8px 0; }
.pp-matrix td { padding: 3px 10px 3px 0; vertical-align: middle; }
.pp-matrix td:first-child { font-family: var(--data-font, monospace); white-space: nowrap; }

/* ============================================================
   Design-system foundation additions (visual-refresh wave 1)
   All rules below use existing tokens only; append-only.
   ============================================================ */

/* --- specificity guard -----------------------------------------------------
   The now-global `input:not(...)` text-control rule computes to a high
   specificity (five :not([attr]) components). These two pre-existing controls
   carried their own look BEFORE the rule went global and must keep winning, so
   they are re-asserted with !important (idiomatic in this file). Without this,
   the top-bar search would lose its pill shape and the report builder its
   compact field padding. */
.topsearch { background: var(--panel) !important; border-radius: 999px !important; padding: 8px 14px !important; font-size: .85rem !important; }
.report-fields select, .report-fields input { padding: 6px 8px !important; }
/* Token/key/URL readouts (API keys, invite links): keep the compact monospace
   blue look. Outside a .form the global rule would otherwise flip them to
   proportional near-white (review finding); this also makes in-form linkboxes
   match the design intent everywhere. */
input.linkbox { font-family: var(--mono, monospace) !important; color: var(--primary-2) !important; padding: 5px 8px !important; font-size: .78rem !important; border-radius: var(--radius-xs) !important; }

/* --- keyboard focus ring ---------------------------------------------------
   Unstyled controls previously lost any focus affordance. A single :focus-visible
   ring covers links, buttons, summaries and every form control (including the
   native ones excluded from the text-control rule). */
a:focus-visible, button:focus-visible, summary:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--radius-sm);
}

/* --- checkbox row ----------------------------------------------------------
   Real `.checkbox` class: box first, label text after, on one row. Mirrors the
   `.toggle` row-override mechanics (re-asserting row layout with !important so it
   beats `.form label`'s flex-column), but without the settings-row border.
   Muted small hints may follow the text. */
.checkbox { display: flex !important; flex-direction: row !important; align-items: center; gap: 8px; color: var(--text) !important; font-weight: 400; cursor: pointer; }
.checkbox input { width: auto; height: auto; flex: 0 0 auto; margin: 0; accent-color: var(--primary); }
.checkbox small, .checkbox .hint { color: var(--muted); font-weight: 400; }

/* --- disclosure (details / summary) ----------------------------------------
   Quiet default styling for advanced/secondary disclosures (pages that want a
   button-like summary keep using `.btnlink` on the <summary>). */
/* Scoped past the two <details> with bespoke chrome: `.usermenu` (profile
   dropdown, base.html) and `.inline-edit` (languages.html table-cell popover
   whose summary is already a `.btnlink`). Everything else gets the quiet box. */
details:not(.usermenu):not(.inline-edit):not(.rail-collapse) { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--panel-2); padding: 0; margin: 8px 0; }
details:not(.usermenu):not(.inline-edit):not(.rail-collapse) > summary { list-style: none; cursor: pointer; padding: 9px 13px; font-size: .88rem; color: var(--text); display: flex; align-items: center; gap: 8px; transition: background .12s; }
details:not(.usermenu):not(.inline-edit):not(.rail-collapse) > summary::-webkit-details-marker { display: none; }
details:not(.usermenu):not(.inline-edit):not(.rail-collapse) > summary::before { content: "▸"; color: var(--muted); font-size: .8em; transition: transform .12s; }
details:not(.usermenu):not(.inline-edit):not(.rail-collapse)[open] > summary::before { transform: rotate(90deg); }
details:not(.usermenu):not(.inline-edit):not(.rail-collapse) > summary:hover { background: var(--panel); }
details:not(.usermenu):not(.inline-edit):not(.rail-collapse)[open] > summary { border-bottom: 1px solid var(--border-soft); }
details:not(.usermenu):not(.inline-edit):not(.rail-collapse) > *:not(summary) { padding: 0 13px 12px; }
/* Progressive-disclosure fieldsets (workflow-builder Add-a-step, Wave 3): the
   <details class="adv"> box is the frame, so a wrapped .lp-fieldset drops its own
   border/margin/padding to avoid a double box (the details rule above pads it). */
details.adv > .lp-fieldset { border: none; margin: 0; padding: 0; }

/* --- file input ------------------------------------------------------------ */
input[type="file"] { background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-dim); padding: 7px 10px; font-size: .88rem; }
input[type="file"]::file-selector-button {
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 5px 12px; margin-right: 10px; font-size: .82rem; font-weight: 600; cursor: pointer; transition: border-color .12s;
}
input[type="file"]::file-selector-button:hover { border-color: var(--primary); }

/* --- select[multiple] (fallback styling; wave 2 replaces the main offender) --- */
select[multiple] { padding: 4px; background: var(--bg-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
select[multiple] option { padding: 4px 8px; border-radius: 4px; }

/* --- truncation / long-name containment ------------------------------------ */
select { max-width: 100%; }
.trunc { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.inline-form select { max-width: 34ch; text-overflow: ellipsis; }

/* --- anchors that render as buttons ----------------------------------------
   `.btnlink` already covers many; these aliases let a plain <a class="btn|ghost|
   btn-secondary"> match the ghost-button look (the compound rules above are
   `button.`-prefixed and don't reach anchors). */
a.btn, a.ghost, a.btn-secondary {
  display: inline-flex; align-items: center; justify-content: center; text-decoration: none;
  background: var(--panel-2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 10px 17px; font-weight: 600; font-size: .9rem; cursor: pointer; box-shadow: none; transition: border-color .12s, filter .15s;
}
a.btn:hover, a.ghost:hover, a.btn-secondary:hover { border-color: var(--primary); filter: none; }
a.btn.small, a.ghost.small, a.btn-secondary.small { padding: 6px 12px; font-size: .82rem; }

/* --- key/value description list (customer overview) ------------------------- */
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 4px 14px; margin: 0; font-size: .88rem; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; color: var(--text); }

/* ============================================================================
   Wave 2B — long-name overflow guard, empty states, narrow utility
   (append-only; existing tokens only; do not edit rules above this point)
   ============================================================================ */

/* --- destructive buttons: a STANDALONE <button class="danger-btn"> (not
   combined with .ghost/.btnlink) was still rendering with the primary
   gradient + no border. The legacy `.danger-btn` rule above only sets
   color/border-color (it was built for ghost/btnlink combos); base <button>
   supplies a gradient background and border:none, so give plain
   button/a.danger-btn the ghost-danger look. Hover is already handled by the
   existing rule above (it uses !important). --- */
button.danger-btn, a.danger-btn {
  background: transparent;
  box-shadow: none;
  border: 1px solid var(--danger-border-solid);
}

/* --- long-name overflow guard: table-row selects. `.rowform` (users.html's
   per-row customer/org pickers) is a different, non-flex inline layout than
   `.inline-form`, so it isn't covered by `.inline-form select`'s 34ch cap
   above. Native HTML table auto-layout grows a column to fit an unclipped
   <select>, so give `.rowform select` the same kind of explicit cap. 26ch (not
   tighter) because `.rowform` also hosts the connector resolve-link pickers
   (connection.html Notion/Help Scout), whose selected page titles should stay
   mostly readable at rest; the open dropdown is always full-width. --- */
.rowform select { max-width: 26ch; text-overflow: ellipsis; }

/* --- narrow utility: replaces repeated `style="max-width:640px"` wrapper
   divs (connection.html webhook/API-endpoint panels). --- */
.narrow { max-width: 640px; }

/* --- empty-state component: quiet centered placeholder for empty lists
   (projects/workflows/glossaries/tm/customers/jobs/users/audit). A subtle
   glyph + one-line message + optional CTA link — no new colors, reuses
   --muted/--border already defined above. */
.empty { text-align: center; padding: 30px 16px; color: var(--muted); }
.empty::before { content: "\22EF"; display: block; font-size: 1.4rem; line-height: 1; margin-bottom: 6px; color: var(--border); letter-spacing: .2em; }
.empty p { margin: 0; font-size: .9rem; }

/* ============================================================================
   Wave 3A — light-theme support: toggle button + literal-color overrides.
   The token block near the top does the heavy lifting (every var() flips per
   theme). Only rules whose colors bypass tokens need explicit light overrides.
   ============================================================================ */

/* Theme toggle (topbar, base.html): a ghost icon button. Overrides the global
   <button> gradient/box-shadow so it reads as a quiet chrome control, matching
   the hamburger. The glyph is CSS-driven off the theme attribute — moon in dark,
   sun in light — so it flips with no JS beyond setting data-theme. */
.themetoggle {
  align-self: center; background: transparent; border: 1px solid var(--border);
  color: var(--text-dim); width: 36px; height: 36px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center; padding: 0;
  font-size: 1rem; line-height: 1; box-shadow: none; cursor: pointer;
  transition: border-color .12s, color .12s;
}
.themetoggle:hover { border-color: var(--primary); color: var(--text); filter: none; }
.themetoggle:active { transform: none; }
.themetoggle::before { content: "\263E"; }               /* ☾ shown in dark mode */
html[data-theme="light"] .themetoggle::before { content: "\2600"; } /* ☀ in light mode */

/* Onboarding re-summon control (SPEC-onboarding-tour D5) — a topbar icon button
   mirroring .themetoggle's shape/tokens so the .topactions cluster stays uniform.
   An <a> (GET /?tour=1 or /help), so it inherits link semantics; the shared sizing
   here keeps it visually identical to the sibling toggle. */
.topaction {
  align-self: center; background: transparent; border: 1px solid var(--border);
  color: var(--text-dim); width: 36px; height: 36px; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center; padding: 0;
  font-size: 1.02rem; line-height: 1; text-decoration: none;
  transition: border-color .12s, color .12s;
}
.topaction:hover { border-color: var(--primary); color: var(--text); }

/* Onboarding "Get started" setup checklist card (SPEC-onboarding-tour Phase 1).
   All colours are existing Kiln tokens; every selector is scoped under
   .onboarding. Static markup — the only motion is the native <details> toggle, so
   there is nothing extra to gate behind prefers-reduced-motion. */
.onboarding { border-color: var(--primary-border); }
.onboarding .ob-details > summary { list-style: none; cursor: pointer; }
.onboarding .ob-details > summary::-webkit-details-marker { display: none; }
.onboarding .ob-summary {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
}
.onboarding .ob-title { margin: 0; }
.onboarding .ob-sub { margin: 3px 0 0; }
.onboarding .ob-progress { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; min-width: 130px; }
.onboarding .ob-meter { width: 150px; }
.onboarding .prog-bar.done { background: var(--success); }
.onboarding .ob-steps { list-style: none; margin: 16px 0 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.onboarding .ob-step {
  display: flex; align-items: center; gap: 12px;
  background: var(--panel-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 11px 13px;
}
.onboarding .ob-step.is-done { border-color: var(--success-border); background: var(--success-soft); }
.onboarding .ob-mark { flex: 0 0 auto; }
.onboarding .ob-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.onboarding .ob-step-title { font-weight: 600; }
.onboarding .ob-step-desc { margin: 0; }
.onboarding .ob-cta { flex: 0 0 auto; margin-left: auto; white-space: nowrap; }
.onboarding .ob-foot { margin-top: 14px; }
.onboarding .ob-dismiss-form { margin: 0; }
.onboarding .ob-open:focus-visible,
.topaction:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* --- Onboarding spotlight (SPEC-onboarding-tour Phase 2) --------------------
   The quiet per-step "Show me" affordance in the checklist. It sits after the
   plain-link CTA and is rendered [hidden] in the template — onboarding.js unhides
   it only when driver.js is present, so with JS disabled it stays invisible/inert
   and the Phase-1 plain links are the whole feature (progressive enhancement). */
.onboarding .ob-showme {
  flex: 0 0 auto; margin-left: 8px; padding: 2px 8px;
  font: inherit; font-size: .82rem; line-height: 1.4;
  color: var(--muted); background: transparent;
  border: 1px solid var(--border); border-radius: var(--radius-xs);
  cursor: pointer; white-space: nowrap;
}
.onboarding .ob-showme:hover { color: var(--text); border-color: var(--primary-border); }
.onboarding .ob-showme:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* --- driver.js popover: Kiln token overrides (One-Kiln Rule) ----------------
   driver.css (loaded BEFORE style.css so these win the cascade) ships hardcoded
   light-only colors. We re-theme its popover/buttons/arrow with EXISTING Kiln
   tokens only, so the spotlight reads correctly in all six skin*theme combos.
   The overlay scrim itself is an <svg fill> set from JS config (dark, both
   themes). No new tokens introduced. */
.driver-popover {
  color: var(--text);
  background-color: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  font-family: var(--font-body, inherit);
}
.driver-popover-title { color: var(--text); }
.driver-popover-description { color: var(--text-dim); }
.driver-popover-progress-text { color: var(--muted); }
.driver-popover-close-btn { color: var(--muted); }
.driver-popover-close-btn:hover,
.driver-popover-close-btn:focus { color: var(--text); }
/* The action ("Take me there" / "Got it") button reads as the primary control. */
.driver-popover-footer-btn {
  color: var(--primary-ink);
  background-color: var(--primary);
  border: 1px solid var(--primary);
  border-radius: var(--radius-xs);
}
.driver-popover-footer-btn:hover,
.driver-popover-footer-btn:focus {
  background-color: var(--primary-2);
  border-color: var(--primary-2);
}
.driver-popover-footer-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* Arrow: recolor only the VISIBLE pointing side per placement to match --panel;
   the three transparent sides stay as driver.css set them. */
.driver-popover-arrow-side-left { border-left-color: var(--panel); }
.driver-popover-arrow-side-right { border-right-color: var(--panel); }
.driver-popover-arrow-side-top { border-top-color: var(--panel); }
.driver-popover-arrow-side-bottom { border-bottom-color: var(--panel); }
/* prefers-reduced-motion: kill driver's fade even if JS animate slips through
   (driver keys its overlay/popover fade off --driver-animation-duration). */
@media (prefers-reduced-motion: reduce) {
  :root { --driver-animation-duration: 0s; }
  .driver-fade .driver-overlay,
  .driver-fade .driver-popover { animation: none !important; }
}

/* Topbar surface. The base `.topbar2` rule now uses var(--bg-2) (a token that
   already flips per theme), so this override is a redundant, explicit reassertion
   kept for clarity + the theme-parity smoke's marker. No blur, no translucency —
   Kiln's bar is flat and solid in both themes. Every other literal in this file
   is a low-alpha state tint (danger/warn/success washes, neutral scrims) that
   reads fine on light once the state *text* tokens above are darkened. */
html[data-theme="light"] .topbar2 { background: var(--bg-2); }

/* ============================================================================
   editor UX (SPEC-editor-ux) — APPEND-ONLY, tokens only. All rules here are
   ADDITIVE (new classes / new state variants) and never modify a rule above.
   Theme-agnostic: every color is a token, so both dark (:root) and
   html[data-theme="light"] are covered automatically.
   ============================================================================ */

/* 1. Status chips, color-by-value (class sits alongside the base `.chip`). These
   join the "Signal dots" family: the machinery section resets the inherited
   `.chip` pill to a borderless dot + uppercase label for exactly these st-*
   selectors, so here we only assign each state's token. st-edited reads as the
   accent "machine touched this" state; st-mt/st-new are the neutral reading. */
.chip.st-approved { --sig: var(--success); --sig-ink: var(--success); }
.chip.st-needs_review { --sig: var(--warn); --sig-ink: var(--warn); }
.chip.st-edited { --sig: var(--primary-2); --sig-ink: var(--primary-2); }
.chip.st-mt, .chip.st-new { --sig: var(--muted); --sig-ink: var(--muted); }

/* 2 + 6. Sticky editor toolbar carrying the confirmed-progress bar + shortcut
   hint. z-index 4 sits just below the topbar (z-index 5); top offset clears it. */
.editor-toolbar {
  position: sticky; top: 62px; z-index: 4;
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 14px; margin: 0 0 14px;
}
.prog-wrap { display: flex; align-items: center; gap: 10px; }
.prog-count { font-size: .82rem; color: var(--text-dim); white-space: nowrap; }
.prog-count b { color: var(--success); font-weight: 700; }
.editor-toolbar .kbd-hint { margin: 0; font-size: .78rem; }
/* flash the bar when confirm-and-advance finds no further unconfirmed row (item 3) */
.prog.flash { animation: prog-flash .6s ease; }
@keyframes prog-flash { 0%, 100% { box-shadow: none; } 50% { box-shadow: 0 0 0 3px var(--ring); } }

/* 4. Unsaved-edit (dirty) + save-failed row accents. Kiln uses a background
   TINT on the whole row, not an inset side-stripe on the first cell (the old
   `box-shadow: inset 3px 0 0` accents are retired per DESIGN.md Components +
   the side-stripe ban). The :hover variants are mandatory: `.table tbody
   tr:hover` (0,2,2) would otherwise replace the status tint with the neutral
   row-hover — hiding the status exactly when the user points at the row. The
   hover tint is a STRONGER step of the same hue, so hover feedback survives
   and dirty/failed stay distinct from each other and from plain hover.
   save-failed follows dirty in source so its danger tint wins a shared row. */
.editor tbody tr.dirty { background: var(--row-dirty); }
.editor tbody tr.dirty:hover { background: var(--row-dirty-hover); }
.editor tbody tr.save-failed { background: var(--row-failed); }
.editor tbody tr.save-failed:hover { background: var(--row-failed-hover); }

/* 5. Autosized targets keep a sensible minimum; JS grows them from scrollHeight. */
.editor .tgt-edit { min-height: 2.6em; overflow: hidden; }

/* 8. Column widths for the editor grid now that a Terms column is present.
   Scoped to `.editor` so document.html's grid (unchanged) is unaffected — equal
   specificity to the base `.grid td.*` rules but declared later, so these win. */
.editor td.src { width: 32%; }
.editor td.tgt { width: 30%; }
.editor td.terms { width: 16%; }

/* ============================================================================
   Project-page header reorg — compact horizontal fact strip + a row of small
   <details> edit disclosures, replacing the old fully-stacked header (name,
   meta, then every edit form full-width, one under another). APPEND-ONLY,
   tokens only, theme-agnostic. Full-width-aware: wraps at any viewport instead
   of overflowing (the .content cap was removed project-wide already).
   ============================================================================ */
.project-header h1 { margin-bottom: 4px; }

/* Facts: a row of labeled key/value tiles (status, due date, content type,
   segmentation, provider(s), target locales) — flows to multiple lines on a
   narrow window instead of ever causing horizontal overflow. */
.fact-strip {
  display: flex; flex-wrap: wrap; gap: 14px 28px;
  margin: 14px 0 4px; padding-top: 14px; border-top: 1px solid var(--border-soft);
}
.fact { display: flex; flex-direction: column; gap: 3px; min-width: 100px; max-width: 100%; }
.fact-lbl { color: var(--muted); font-size: .75rem; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; }
.fact-val { font-size: .88rem; color: var(--text-dim); display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* Target locales gets the full row width so its chip list can wrap freely
   instead of squeezing into a fixed-width column. */
.fact-wide { flex: 1 1 260px; }
.fact-wide .chips { margin-top: 1px; }

/* Edit affordances: every form lives behind a small collapsed <details>: a
   horizontal row of `.btnlink small` summaries (project.html's existing
   `.inline-edit` details already render chrome-free — only the summary is
   styled — so stacking them in a flex row reads as one compact toolbar). Each
   details' body still opens as a normal block below the row when expanded. */
.edit-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; align-items: flex-start; }
.edit-row > details.inline-edit { margin: 0; }
.edit-row > details.inline-edit > summary { margin: 0; }

/* Conditional-workflow builder graph (SPEC-conditional-workflows Phase 4). A
   read-only, server-rendered SVG aid next to the step list; theme-aware via the
   same CSS variables. The SVG scales down on narrow viewports (max-width:100%)
   and never forces the page to scroll horizontally. */
.wf-graph { margin: 6px 0 2px; }
.wf-graph figcaption { margin-bottom: 6px; }
.wf-graph-svg { max-width: 100%; height: auto; display: block; }
.wfg-title { font-size: 12px; font-weight: 600; fill: var(--text); }
.wfg-sub { font-size: 10.5px; fill: var(--muted); }
/* Phase 3.1 per-node META line (packaged assets/gates). Muted, one notch smaller
   than the role/model sub-line; same semantic token so it reads calm in all skins. */
.wfg-meta { font-size: 10px; fill: var(--muted); }
.wfg-elabel { font-size: 10px; font-weight: 600; }
/* Run-view overlay (SPEC-conditional-workflows Phase 4 deferred item): a per-node
   status label (drawn right-aligned inside the node) + an inline figcaption note. Colour
   comes from the status token set inline on the <text> fill (success/danger/muted/primary/
   warn), so no new colour pair and the contrast checker is untouched; the TEXT label makes
   it colourblind-safe. */
.wfg-state { font-size: 10px; font-weight: 600; }
.wfg-run-note { display: inline-block; margin-left: 8px; font-size: .72rem; }
/* Phase 3 shape line: primary + additive chips and a one-line headline that introduces
   the workflow diagram. LAYOUT ONLY — colours come from the existing .chip / .muted
   tokens the markup already carries (the review headline inherits body --text for a
   prominent line; the builder headline carries .muted). No new colour pair. */
.wf-shape-line { margin: 12px 0 4px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.wf-shape-headline { flex-basis: 100%; margin: 4px 0 0; font-size: .92rem; }
/* legend keys in the figcaption */
.wfg-k { display: inline-block; margin-left: 8px; font-weight: 600; font-size: .72rem; }
.wfg-pass { color: var(--success); }
.wfg-fail { color: var(--danger); }
.wfg-loop { color: var(--danger); }

/* SPEC-workflow-canvas Phase 1 — drag-to-reorder affordances on the BUILDER graph
   only (the macro emits them solely under editable=true; every other call site's SVG
   is byte-identical to before). LAYOUT + CURSOR ONLY: no new colour pair — the grip
   reuses --border/--muted like the node outlines, the insertion marker and the "run
   together" band highlight reuse --success, exactly the token the pass edge already
   uses. The slot/band rects themselves are fill:none and therefore never hit-tested by
   the browser; wfcanvas.js hit-tests their client rects itself. */
.wf-canvas { position: relative; }
/* touch-action MUST be static on the grip, not toggled by a class at pointerdown.
   Per Pointer Events L2 the browser decides whether a gesture is a scroll or a drag from
   the touch-action value in force when the gesture STARTS; a class added in the
   pointerdown handler is already one gesture too late, so the first touch-drag on a
   phone scrolled the page and fired pointercancel instead of moving the step. */
.wf-canvas .wfg-grip { cursor: grab; touch-action: none; }
/* Belt and braces: touch-action on an SVG CHILD element (the grip is a <g>) has less
   certain browser support than on the svg root, which is a replaced/box element. Setting
   it on the root too guarantees the browser has the answer before the gesture starts,
   whichever element it consults. The graph does not scroll internally, so suppressing
   touch panning over it costs nothing. */
.wf-canvas .wf-graph-svg { touch-action: none; }
.wf-canvas .wfg-node.wfg-dragging { cursor: grabbing; opacity: .65; }
.wf-canvas .wfg-node.wfg-dragging .wfg-grip { cursor: grabbing; }
/* During a drag the non-dragged nodes stop swallowing text selection so the pointer
   stream stays clean. */
.wf-graph-svg.wfg-drag-active { user-select: none; }
.wf-canvas-notice { margin: 6px 0 0; font-size: .85rem; color: var(--muted); }
.wf-canvas-notice.error { color: var(--danger); font-weight: 600; }

/* Run View v2 #1 — per-step-visit Gantt on /runs/{id}, under the run graph. LAYOUT
   ONLY over the existing semantic tokens (no new colour pair): row/name text on
   --text/--muted, the bar TRACK on --bg-2, and each bar FILL is a status hue token
   (var(--success)/--warn/--danger/--primary/--muted) that is redundant with the
   status_badge + label the row also carries (colourblind-safe — colour is never the
   sole signal). All selectors are scoped under .rv-gantt so nothing leaks page-wide. */
.rv-gantt { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 10px;
  overflow-x: auto; }
.rv-gantt-h { font-size: .95rem; margin: 0 0 2px; }
.rv-gantt-rows { min-width: 460px; margin-top: 8px; }
.rv-gantt-row, .rv-gantt-foot {
  display: grid; grid-template-columns: minmax(120px, 1.6fr) auto minmax(90px, 3fr) 62px 92px;
  align-items: center; gap: 10px; padding: 4px 0; }
.rv-gantt-foot { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 8px; }
.rv-gantt-name { font-size: .84rem; color: var(--text); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.rv-gantt-orphan .rv-gantt-name { color: var(--muted); font-style: italic; }
.rv-gantt-visit { margin-left: 3px; font-size: .74rem; color: var(--muted); }
.rv-gantt-badge { justify-self: start; }
.rv-gantt-track { width: 100%; min-width: 60px; display: flex; align-items: center; }
.rv-gantt-svg { width: 100%; height: 12px; display: block; }
.rv-gantt-bg { fill: var(--bg-2); }
.rv-gantt-dur { font-size: .78rem; color: var(--text); text-align: right; }
.rv-gantt-cost { font-size: .78rem; color: var(--muted); text-align: right; }
.rv-gantt-foot-wall { justify-content: flex-start; }

/* SPEC-workflows-redesign Phase 4a — describe-to-multilingual made VISIBLE (review
   screen fan-out rows + confirm copy) and the group page coverage table. LAYOUT + the
   existing semantic tokens ONLY — no new colour pair, so every skin and the contrast
   checker are unaffected: locale codes/labels read on --text/--muted, row dividers use
   --border, and the role/coverage chips reuse the shared .chip token. */
.wf-fanout { margin: 4px 0 14px; }
.wf-fanout > h2 { margin: 0 0 4px; }
.wf-ml-rows { list-style: none; margin: 8px 0 0; padding: 0; }
.wf-ml-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px;
  padding: 6px 0; border-top: 1px solid var(--border); }
.wf-ml-row:first-child { border-top: 0; }
.wf-ml-code { min-width: 6.5em; font-weight: 600; }
.wf-ml-label { flex: 1 1 auto; min-width: 0; color: var(--muted); }
.wf-ml-role { flex: 0 0 auto; }
.wf-confirm-line { margin: 10px 0; }
/* Group page coverage: a calm table; the locale is monospace, the fallback note muted.
   No new colours — "no coverage" leans on the existing .muted token, not a red. */
.wf-cov { margin: 6px 0 2px; }
.wf-cov-note { margin: 2px 0 10px; }
.wf-cov td.wf-cov-fallback { color: var(--muted); }

/* B0 per-step PACKAGE / VERIFY panel (SPEC-workflows-redesign Track B). A calm,
   read-only two-column readout in a collapsed <details> under each step. LAYOUT +
   the existing semantic tokens only — no new colour pair (the scope tag reuses
   --muted, the divider --border), so it reads correct in every skin and passes the
   contrast checker (all text is --text/--muted on --panel/--card, unchanged pairs). */
.wf-pkg { margin: 6px 0 2px; }
.wf-pkg > summary { font-size: .86rem; color: var(--muted); }
.wf-pkg-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 8px; }
.wf-pkg-col { min-width: 0; }
.wf-pkg-h { margin: 0 0 6px; font-size: .82rem; font-weight: 600; color: var(--text); }
.wf-pkg-list { margin: 0; }
.wf-pkg-row { display: grid; grid-template-columns: minmax(6.5em, 9em) 1fr; gap: 8px;
              padding: 3px 0; border-top: 1px solid var(--border); }
.wf-pkg-row:first-child { border-top: 0; }
.wf-pkg-k { margin: 0; font-size: .8rem; font-weight: 600; color: var(--muted); }
.wf-pkg-v { margin: 0; font-size: .8rem; color: var(--text); word-break: break-word; }
.wf-pkg-scope { color: var(--muted); font-style: italic; white-space: nowrap; }
/* Narrow viewports: stack the two columns so neither overflows horizontally. */
@media (max-width: 680px) {
  .wf-pkg-cols { grid-template-columns: 1fr; gap: 10px; }
}

/* LLM Ledger viewer (/prompt-tap). Layout only — reuses existing tokens (no new
   color pairs), scoped under .ptap-* so nothing leaks app-wide. */
.filterbar { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; margin: 8px 0 12px; }
.ptap-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.ptap-rec { padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; }
.ptap-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ptap-ts { margin-left: auto; }
.ptap-meta { margin: 6px 0 2px; }
/* Correlation facts are separate spans; the separator is inserted BETWEEN them so a
   capture with only some of them never leads with a stray dot. */
.ptap-meta > span + span::before { content: " \00b7  "; }
.ptap-rec > details.adv { margin-top: 6px; }
.ptap-rec pre.code { max-height: 22rem; overflow: auto; }
/* Download row: the format links sit inline with their explanatory note. */
.ptap-dl { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }

/* ============================================================================
   Run-envelope viewer (proposal §9): translate ACTION -> language-pair RUN ->
   nested CALLS, as native <details> disclosures. Two levels of nesting, each a
   quiet surface step so the hierarchy reads at a glance without cards-in-cards.
   Density with hierarchy (PRODUCT.md §3); state carried by the shared badges.
   ============================================================================ */
.tap-actions { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.tap-action { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--panel); overflow: hidden; }
.tap-action > details > summary {
  list-style: none; cursor: pointer; padding: 11px 14px;
  display: grid; grid-template-columns: 1fr auto; grid-auto-rows: min-content;
  gap: 4px 14px; align-items: center;
}
.tap-action > details > summary::-webkit-details-marker { display: none; }
.tap-action > details > summary::before {
  content: "\25B8"; color: var(--muted); font-size: .8rem;
  transition: transform .15s ease; grid-row: 1 / span 2; align-self: center;
  margin-right: 2px;
}
.tap-action > details[open] > summary::before { transform: rotate(90deg); }
.tap-action-sum { display: contents; }
.tap-action-lead { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.tap-action-title { font-weight: 600; color: var(--text); }
.tap-action-title .muted { font-weight: 400; }
.tap-action-locales { grid-column: 1; color: var(--text-dim); font-size: .8rem; }
.tap-metrics { grid-column: 2; grid-row: 1 / span 2; display: flex; align-items: center; gap: 12px; justify-self: end; text-align: right; flex-wrap: wrap; }
.tap-metrics .danger-text, .tap-run-metrics .danger-text { color: var(--danger); font-weight: 600; }

.tap-runs { padding: 4px 12px 12px 30px; background: var(--bg-2); border-top: 1px solid var(--border-soft); }
.tap-wf { margin: 8px 2px; }
.tap-run { border: 1px solid var(--border-soft); border-radius: var(--radius-xs); margin-top: 8px; background: var(--panel); }
.tap-run > summary {
  list-style: none; cursor: pointer; padding: 8px 12px;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.tap-run > summary::-webkit-details-marker { display: none; }
.tap-run > summary::before { content: "\25B8"; color: var(--muted); font-size: .75rem; transition: transform .15s ease; }
.tap-run[open] > summary::before { transform: rotate(90deg); }
.tap-run-locale { font-weight: 600; color: var(--text); }
.tap-run-metrics { display: flex; align-items: center; gap: 12px; margin-left: auto; flex-wrap: wrap; }

.tap-calls { list-style: none; margin: 0; padding: 4px 12px 12px; display: flex; flex-direction: column; gap: 8px; }
.tap-call { padding: 9px 11px; border: 1px solid var(--border-soft); border-radius: var(--radius-xs); background: var(--bg-2); }
.tap-call-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tap-call-ts { margin-left: auto; }
.tap-call-meta { margin: 6px 0 2px; }
.tap-call-meta > span + span::before { content: " \00b7  "; }
.tap-call > details.adv { margin-top: 6px; }
.tap-call pre.code { max-height: 20rem; overflow: auto; }
.tap-more, .tap-empty-run { margin: 6px 12px 10px; }

/* A quieter chip variant for the descriptor tags (kind / batch) so they don't
   compete with the value chips inside a call. */
.chip.soft { background: transparent; color: var(--muted); }
.mono, .chip.mono { font-family: var(--data-font, var(--mono, ui-monospace, monospace)); font-variant-numeric: tabular-nums; }

/* The flat "All calls" fallback is a secondary disclosure; its summary carries an
   inline heading rather than the default marker. */
.tap-flat > summary { cursor: pointer; list-style: none; }
.tap-flat > summary::-webkit-details-marker { display: none; }
.tap-flat > summary::before { content: "\25B8 "; color: var(--muted); transition: transform .15s ease; display: inline-block; }
.tap-flat[open] > summary::before { transform: rotate(90deg); }
.inline-h2 { display: inline; font-size: 1.05rem; }

@media (max-width: 640px) {
  .tap-action > details > summary { grid-template-columns: 1fr; }
  .tap-metrics { grid-column: 1; grid-row: auto; justify-self: start; text-align: left; }
  .tap-runs { padding-left: 14px; }
}

/* ============================================================================
   Reduced motion (accessibility floor, DESIGN.md §5 Focus + PRODUCT.md a11y).
   Kiln's motion is state-conveying only (150–250ms); for users who ask for
   reduced motion, collapse every transition/animation to a near-instant change
   and disable smooth scrolling. Kept last so it wins the cascade. The tiny
   non-zero duration preserves transitionend/animationend callbacks that JS may
   rely on (e.g. the confirm-and-advance flash) instead of dropping them.
   ============================================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ============================================================================
   Account -> Appearance: the per-user skin picker (SPEC-ui-skins). Append-only,
   tokens only, theme- + skin-agnostic (every color is a token, so it themes
   itself under whichever skin is active). The .skin-dot swatches are the ONLY
   deliberate literal colors here — they are a preview of each OTHER skin's
   identity hues (from services.skins.SKINS), so they must not track the active
   theme; the surrounding chrome is fully tokenized.
   ============================================================================ */
.skin-options { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; margin: var(--space-2) 0 var(--space-4); }
/* Re-assert row layout at >= specificity so `.form label`'s flex-column (which
   would stack the radio above the swatches) does not win — the .lp-opt precedent. */
.form label.skin-option, .skin-option {
  display: flex; flex-direction: row; align-items: center; gap: 11px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 11px 13px; cursor: pointer; font-weight: 400; text-align: left;
  transition: border-color .12s, background .12s;
}
.skin-option:hover { border-color: var(--hover-border); }
.skin-option.on { border-color: var(--primary); background: var(--primary-soft); }
.skin-option input { flex: 0 0 auto; margin: 0; accent-color: var(--primary); }
.skin-dots { display: inline-flex; gap: 3px; flex: 0 0 auto; }
.skin-dot { width: 15px; height: 15px; border-radius: 50%; border: 1px solid var(--border); }
.skin-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.skin-name { font-weight: 600; color: var(--text); font-size: .9rem; }
.skin-desc { color: var(--muted); font-size: .8rem; }

/* ============================================================================
   Per-page polish — Wave A (dashboard / projects / project / document).
   Hierarchy, primary-action clarity, genuine empty states, spacing rhythm.
   APPEND-ONLY, existing tokens only (skin token-parity untouched), no class/id
   renames (JS hooks preserved), theme- + skin-agnostic.
   ============================================================================ */

/* Genuine empty state: quiet, left-aligned, and always ending in a clear next
   action — not decorative filler. Lighter than the full-card `.empty` treatment;
   used for a section that has no rows yet but lives inside a populated screen. */
.emptystate { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); padding: var(--space-3) 0 var(--space-1); color: var(--muted); font-size: .875rem; }
.emptystate p { margin: 0; }

/* Action-bar grouping: a hairline divider separating the primary/run group from
   the secondary/navigate group inside a `.row-actions` flow, so the Translate
   primary is not lost in a wall of equal-weight controls (document Segments). */
.row-actions .act-sep { align-self: stretch; width: 1px; margin: 3px 3px; background: var(--border-soft); }

/* Project Overview: lead the metric readout with a real progress bar (a non-tile
   element that breaks the identical-tile grid) so approved-% reads as the primary
   metric and the remaining resource counts become a secondary detail row. */
.overview-progress { display: flex; align-items: center; gap: var(--space-3); margin: 0 0 var(--space-4); flex-wrap: wrap; }
.overview-progress .prog { width: clamp(160px, 40%, 320px); height: 8px; }

/* ============================================================================
   Visualization quick-wins (VIZ-RESEARCH-2026-07-21) — TM-leverage stacked bar.
   Signature loc-analytics visual (Lokalise match-distribution / Crowdin Savings
   Summary analog): a single horizontal part-to-whole bar, band colors REUSE the
   existing reserved-status palette (never a new hue) so it stays consistent with
   the segment-status chips / TM-match badges elsewhere. Scoped to .card .tmlev
   (its page ancestor, per the tokens-only / scoped-selectors rule) — it only ever
   renders inside a card on project.html Overview or reports.html.
   ============================================================================ */
.card .tmlev { margin-top: var(--space-3); }
.card .tmlev-heading {
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin: 0 0 6px;
}
.card .tmlev-bar {
  display: flex; height: 14px; border-radius: 4px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--border-soft);
}
.card .tmlev-seg { height: 100%; }
.card .tmlev-seg.tmlev-exact { background: var(--success); }
.card .tmlev-seg.tmlev-fuzzy { background: var(--warn); }
.card .tmlev-seg.tmlev-new { background: var(--muted); }
.card .tmlev-seg.tmlev-untranslated { background: var(--border); }
.card .tmlev-legend {
  list-style: none; display: flex; flex-wrap: wrap; gap: 6px 16px;
  margin: 8px 0 0; padding: 0; font-size: .82rem; color: var(--text-dim);
}
.card .tmlev-key { display: inline-flex; align-items: center; gap: 6px; }
.card .tmlev-key b { margin-left: 2px; color: var(--text); font-weight: 700; }
.card .tmlev-swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; flex: none; }
.card .tmlev-key.tmlev-exact .tmlev-swatch { background: var(--success); }
.card .tmlev-key.tmlev-fuzzy .tmlev-swatch { background: var(--warn); }
.card .tmlev-key.tmlev-new .tmlev-swatch { background: var(--muted); }
.card .tmlev-key.tmlev-untranslated .tmlev-swatch { background: var(--border); }

/* CV-1: the 7-band WORD-based leverage bar. Three hue FAMILIES (covered=leverage
   green, fuzzy=amber, new=muted), the covered/fuzzy tiers separated by opacity so
   seven labelled bands stay legible without inventing seven distinct hues. Reuses
   the tmlev-bar geometry. */
.card .lev7-bar {
  display: flex; height: 14px; border-radius: 4px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--border-soft);
}
.card .lev7-seg { height: 100%; }
.card .lev7-seg.lev7-repetitions { background: var(--success); opacity: .6; }
.card .lev7-seg.lev7-context     { background: var(--success); opacity: .8; }
.card .lev7-seg.lev7-exact       { background: var(--success); }
.card .lev7-seg.lev7-fuzzy_high  { background: var(--warn); opacity: .55; }
.card .lev7-seg.lev7-fuzzy_mid   { background: var(--warn); opacity: .75; }
.card .lev7-seg.lev7-fuzzy_low   { background: var(--warn); }
.card .lev7-seg.lev7-new         { background: var(--muted); }
.card .lev7-legend {
  list-style: none; display: flex; flex-wrap: wrap; gap: 6px 16px;
  margin: 8px 0 0; padding: 0; font-size: .82rem; color: var(--text-dim);
}
.card .lev7-key { display: inline-flex; align-items: center; gap: 6px; }
.card .lev7-key b { margin-left: 2px; color: var(--text); font-weight: 700; }
.card .lev7-swatch { width: 10px; height: 10px; border-radius: 2px; display: inline-block; flex: none; }
.card .lev7-key.lev7-repetitions .lev7-swatch { background: var(--success); opacity: .6; }
.card .lev7-key.lev7-context .lev7-swatch     { background: var(--success); opacity: .8; }
.card .lev7-key.lev7-exact .lev7-swatch       { background: var(--success); }
.card .lev7-key.lev7-fuzzy_high .lev7-swatch  { background: var(--warn); opacity: .55; }
.card .lev7-key.lev7-fuzzy_mid .lev7-swatch   { background: var(--warn); opacity: .75; }
.card .lev7-key.lev7-fuzzy_low .lev7-swatch   { background: var(--warn); }
.card .lev7-key.lev7-new .lev7-swatch         { background: var(--muted); }

/* ============================================================================
   Visualization quick-wins — the spend-cap meter (DESIGN.md §5 "Control Surfaces"
   specifies this exact module: Label-role title, large Data-role spend figure, a
   9px meter with terracotta fill + an Amber Hold cap tick at the auto-pause
   threshold). It never existed as a graphic before this wave — the spend banner
   was text-only. Scoped to .card .spend-meter (setup_ai.html's Spend cap card)
   and .warnbar.spendbar (base.html's app-shell trip banner).
   ============================================================================ */
.card .spend-meter { margin: 6px 0 12px; }
.card .spend-meter-title {
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin: 0 0 4px;
}
.card .spend-meter-figure { font-size: 1.55rem; font-weight: 750; color: var(--text); margin: 0 0 8px; }
.card .spend-meter-of { font-size: 1rem; font-weight: 600; color: var(--text-dim); }
.card .spend-meter-track { position: relative; max-width: 420px; margin-bottom: 6px; }
.card .spend-meter-track .prog { width: 100%; height: 9px; }
.card .spend-meter-track .prog-bar.spend-danger { background: var(--danger); }
.card .spend-meter-tick {
  position: absolute; top: -2px; width: 2px; height: 13px;
  background: var(--warn); border-radius: 1px; transform: translateX(-1px); pointer-events: none;
}
.card .spend-meter-legend { margin: 0; display: flex; align-items: center; gap: 8px; }
/* CV-5: the compact per-department token-usage meter in the budgets table cell —
   a small .prog track + a percent readout, reusing the danger fill for over-cap. */
.usage-bar { display: flex; align-items: center; gap: 8px; }
.usage-bar .prog { width: 90px; height: 6px; }
.usage-bar-pct { color: var(--text-dim); white-space: nowrap; }
/* the app-shell trip banner: a compact always-danger meter beneath the existing
   text (the banner only renders when the cap IS tripped, so this is always the
   at/over-cap state — no tick needed, the numbers are in the text right above). */
.warnbar.spendbar .prog { margin-top: 8px; max-width: 300px; height: 6px; }
.warnbar.spendbar .prog-bar { background: var(--danger); }

/* ============================================================================
   Per-page polish — Wave B (automation surfaces: editor, workflow builder,
   author-review, connectors, connector_type, connection). Hierarchy, calm
   sectioning of long forms, primary-action clarity, and a genuine alert
   affordance. APPEND-ONLY, existing tokens only (skin token-parity untouched),
   no class/id renames — the editor --row-* state tints, every JS hook, and the
   read-only SVG step graph are preserved. Theme- + skin-agnostic.
   ============================================================================ */

/* Notice / callout: a real alert affordance for the "could not be created",
   "review these before creating", and "some steps were LLM-adapted" messages
   that shipped as a stretched pill (display:block on a .badge — a badge is a
   reading, not an alert). The neutral base is dim ink on the second neutral
   layer; -warn reuses the contrast-proven warn combo (identical values to
   .badge.warn) so no new color pair is introduced. */
.notice { padding: var(--space-3) var(--space-4); border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--panel-2); color: var(--text-dim);
  font-size: .875rem; margin: var(--space-3) 0; max-width: var(--readable); }
.notice > :first-child { margin-top: 0; }
.notice > :last-child { margin-bottom: 0; }
.notice strong { color: var(--text); }
.notice ul { margin: var(--space-1) 0 0; padding-left: 1.2em; }
.notice ul li { margin: 2px 0; }
.notice-warn { background: var(--warn-soft); border-color: var(--warn-border); color: var(--warn); }
.notice-warn strong { color: inherit; }

/* Connector catalog (connectors.html only): on that page the Integrations tiles
   are .tile-link and lift via the base .tile:hover rule (kept). The MT-engine +
   roadmap tiles are non-navigational, so a hover lift there is a false
   affordance — suppress it, but scope the reset to the connectors static grids
   (.tilegrid-static, a marker added to just those two grids) so setup_ai.html's
   non-link .tile hover is byte-unaffected (it shares the app-wide .tilegrid). */
.tilegrid-static .tile:hover { transform: none; }
/* Roadmap (planned, not-yet-available) tiles read as clearly secondary. The
   .tile-inert class is used only on connectors.html's roadmap tiles. */
.tile-inert { opacity: .74; }
.tile-inert:hover { border-color: var(--border); }

/* Workflow builder step cards: lift the step NAME to primary within a dense
   chip row and give each block a hover + calmer vertical rhythm, so the list
   reads as an ordered sequence rather than a wall. The chips/badges are emitted
   unchanged (branch, key, QE gate, guard … are load-bearing state readouts).
   ALL scoped under .steplist — the workflow_builder step list's stable ancestor
   — so run_view.html (which uses .runstep + a bare .step-head, no .steplist) is
   byte-unaffected. */
.steplist .step { transition: border-color .12s; }
.steplist .step:hover { border-color: var(--hover-border); }
.steplist > li.step { margin: 10px 0; }
.steplist .step-head > strong:first-child { font-size: .95rem; color: var(--text); margin-right: 2px; }

/* Long connector forms (connector_type / connection): the Settings and
   Credentials groups now sit inside fieldset legends for calm sectioning. Keep
   the "· stored encrypted" / "· leave blank to keep" legend notes quiet so they
   don't inherit the legend's 600 weight and shout. */
.form fieldset.lp-fieldset > legend .muted { font-weight: 400; }

/* ============================================================================
   Per-page polish — Wave C (data & operations surfaces: tm / tm_detail /
   glossaries / glossary_detail / jobs / settings / reports).
   Glanceable status, ledger-grade number columns, genuine actionable empty
   states. APPEND-ONLY, existing tokens only (skin token-parity untouched), no
   class/id renames (JS hooks preserved), theme- + skin-agnostic.
   ============================================================================ */

/* Status badges as GLANCEABLE signal dots (jobs.html; DESIGN §5 "the machine is
   working" = accent, failure = Vermilion Stop, held = Amber Hold). .run is the
   in-progress/running "the machine is working" state — accent dot that PULSES
   (one expanding ring, see the machinery section). .danger is the failed state —
   Vermilion Stop dot + label. Both map to existing tokens, so the state-colour-
   on-surface contrast the six-combo gate now enforces holds in all six combos. */
.badge.run { --sig: var(--primary-2); --sig-ink: var(--primary-2); }
.badge.danger { --sig: var(--danger); --sig-ink: var(--danger); }

/* Progress at 100% turns Growth Green (DESIGN §5 progress spec) so a finished
   job's bar is unmistakable from an in-flight terracotta one. Scoped to the
   `.done` modifier — only jobs.html sets it; every other .prog-bar is untouched. */
.prog-bar.done { background: var(--success); }

/* Report bars: give the VALUE column the Tabular Ledger treatment (DESIGN
   "The Tabular Ledger Rule" — any column of numbers is mono/tabular, right-
   aligned where compared). The label already right-aligns toward its bar; the
   value now sits in a fixed mono column so counts line up down the stack. */
.barval { font-family: var(--data-font); font-variant-numeric: tabular-nums; text-align: right; color: var(--text-dim); }

/* Genuine, actionable empty state for a filtered table that returned nothing:
   reuse the Wave-A `.emptystate` row (quiet, ends in a clear next action) so a
   no-match result offers a way back instead of a dead end. */
.emptystate a:not(.btnlink) { color: var(--primary-2); font-weight: 550; }

/* ============================================================================
   Batch language-matrix run monitor (SPEC-workflows-redesign Phase 5b).
   Page-scoped to batch_view.html. TOKENS ONLY — every colour is an existing
   :root token (mirrored across all four skins + light), so the status chips
   below inherit each skin automatically with NO new token to keep in parity.
   The status chips reuse the SAME success/warn/primary/danger families the
   segment-status chips (.chip.st-*) already use; failed reuses the danger row
   wash (--row-failed) that has a value in every skin.
   ============================================================================ */
.bm-agg { margin: 6px 0; }
.bm-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.bm-inline { display: inline; margin: 0; }
.bm-refresh { color: var(--primary-2); font-weight: 550; text-decoration: none; }
.bm-refresh:hover { text-decoration: underline; }

/* The matrix: compact, tabular-ledger cells. Keeps .table chrome; only tightens
   the cell padding so a wide step axis stays scannable, and centres the chips. */
.bm-matrix th, .bm-matrix td { padding: 8px 10px; white-space: nowrap; }
.bm-matrix td.bm-cell { text-align: center; }
.bm-matrix thead th { vertical-align: bottom; }
.bm-legend { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }

/* Per-cell / per-row status chips, color-by-status. Same "Signal dots" family as
   the .chip.st-* chips (machinery section resets these bm-* selectors to a
   borderless dot + uppercase label). bm-run is the live "running" state and
   PULSES; bm-fail is Vermilion Stop; bm-rev/bm-part are Amber Hold; the rest are
   the neutral reading. Values are the same success/warn/primary-2/danger tokens
   the six-combo contrast gate now covers on --panel/--panel-2. */
.chip.bm-ok      { --sig: var(--success); --sig-ink: var(--success); }
.chip.bm-run     { --sig: var(--primary-2); --sig-ink: var(--primary-2); }
.chip.bm-rev     { --sig: var(--warn); --sig-ink: var(--warn); }
.chip.bm-fail    { --sig: var(--danger); --sig-ink: var(--danger); }
.chip.bm-part    { --sig: var(--warn); --sig-ink: var(--warn); }
.chip.bm-skip    { --sig: var(--muted); --sig-ink: var(--muted); }
.chip.bm-na      { --sig: var(--muted); --sig-ink: var(--muted); }
.chip.bm-pending { --sig: var(--muted); --sig-ink: var(--text-dim); }

/* Heatmap tint (VIZ-RESEARCH-2026-07-21 quick-win): a light alpha wash of the
   SAME status-family color the chip already carries, painted on the cell behind
   it, so the eye finds hot cells at a glance across a wide locale x step matrix.
   REDUNDANT reinforcement only — the chip + label remain the actual signal, this
   never stands alone (a cell with no chip, e.g. an em-dash "not in this variant",
   gets no tint class and stays untinted). color-mix() keeps this to EXISTING
   tokens (--success/--primary-2/--warn/--danger/--muted, already six-combo
   contrast-checked as text/border colors) rather than inventing six new -soft
   background tokens; percentages are kept low (8-14%) to match the existing
   *-soft token family's alpha range. */
.bm-matrix td.bm-cell.bm-ok      { background: color-mix(in srgb, var(--success) 12%, transparent); }
.bm-matrix td.bm-cell.bm-run     { background: color-mix(in srgb, var(--primary-2) 12%, transparent); }
.bm-matrix td.bm-cell.bm-rev     { background: color-mix(in srgb, var(--warn) 14%, transparent); }
.bm-matrix td.bm-cell.bm-fail    { background: color-mix(in srgb, var(--danger) 12%, transparent); }
.bm-matrix td.bm-cell.bm-part    { background: color-mix(in srgb, var(--warn) 14%, transparent); }
.bm-matrix td.bm-cell.bm-skip,
.bm-matrix td.bm-cell.bm-na,
.bm-matrix td.bm-cell.bm-pending { background: color-mix(in srgb, var(--muted) 8%, transparent); }

/* Cell adornments: the LQA/QE score + the loop visit-count ride next to the
   chip in the data font; the em-dash marks a step a diverged variant lacks. */
.bm-score { color: var(--text-dim); font-size: .78rem; margin-left: 2px; }
.bm-visits { color: var(--muted); font-size: .74rem; margin-left: 2px; }
.bm-dash { color: var(--muted); }
.bm-div { color: var(--warn); font-weight: 700; margin-left: 3px; }
.bm-rowaction { text-align: right; }

/* ============================================================================
   Workflows create surface: "Design a workflow" info affordance (copy-density
   cleanup -- replaces the old stacked heading + two paragraphs with one h2 and
   a hover/focus tooltip). Page-scoped under .wf-create (the create-surface
   .card's own marker, added alongside it) so this circular-badge treatment
   doesn't leak onto any other title=-carrying element app-wide (past waves
   shipped unscoped selectors that bled onto neighbour pages -- see the
   .steplist / .tilegrid-static scoping fix). TOKENS ONLY: --panel-2/--border/
   --muted mirror the existing .badge resting state, so all four skins + light
   inherit with no new token. */
.wf-create h2 { display: flex; align-items: center; gap: 8px; }
.wf-create .wf-info-btn {
  width: 18px; height: 18px; padding: 0; display: inline-grid; place-items: center;
  border-radius: 50%; background: var(--panel-2); color: var(--muted);
  border: 1px solid var(--border); font-size: .72rem; font-weight: 700;
  font-style: italic; line-height: 1; cursor: help; box-shadow: none;
}
.wf-create .wf-info-btn:hover { border-color: var(--primary); color: var(--primary-2); filter: none; }
/* No .wf-info-btn:focus-visible rule needed -- it's a <button>, so the
   app-wide button:focus-visible ring (defined earlier in this file) already
   applies without any scoped override. */

/* ============================================================================
   Signal-dot machinery (Option A status indicators — planning/design/status-
   plates.html). Shared geometry for the badge + status-chip families: the
   coloured DOT (::before), the live PULSE ring (::after on the working/live
   states), the HOLLOW dot (Beta / Semantic), the SQUARED dot (Paused), the
   narrow-width label collapse, and the reduced-motion degrade. Colours arrive
   through the per-variant --sig / --sig-ink custom props assigned above; every
   value resolves to an EXISTING token, so all four skins + light inherit with
   NO new :root token (skin token-parity untouched). This also RESETS the base
   `.chip` pill for the st- and bm- status subsets only (base `.chip` — role /
   coverage / removable chips — keeps its pill). APPEND-ONLY, tokens only.
   ============================================================================ */
/* STATUS-scoped: a `.badge` gets the dot treatment only with a state modifier or
   the explicit `.status` marker — never bare (see the base-badge note above). */
.badge.status, .badge.run, .badge.ok, .badge.warn, .badge.danger, .badge.exact,
.badge.fuzzy, .badge.semantic, .badge.reauth, .badge.paused, .badge.beta,
.chip.st-approved, .chip.st-needs_review, .chip.st-edited, .chip.st-mt, .chip.st-new,
.chip.bm-ok, .chip.bm-run, .chip.bm-rev, .chip.bm-fail, .chip.bm-part,
.chip.bm-skip, .chip.bm-na, .chip.bm-pending {
  display: inline-flex; align-items: center; gap: 6px; position: relative;
  background: transparent; border: 0; padding: 0; border-radius: 0;
  font-size: .72rem; font-weight: 600; line-height: 1.4; letter-spacing: .05em;
  text-transform: uppercase; white-space: nowrap; vertical-align: middle;
  color: var(--sig-ink, var(--muted));
}
/* the dot itself */
.badge.status::before, .badge.run::before, .badge.ok::before, .badge.warn::before,
.badge.danger::before, .badge.exact::before, .badge.fuzzy::before, .badge.semantic::before,
.badge.reauth::before, .badge.paused::before, .badge.beta::before,
.chip.st-approved::before, .chip.st-needs_review::before, .chip.st-edited::before,
.chip.st-mt::before, .chip.st-new::before,
.chip.bm-ok::before, .chip.bm-run::before, .chip.bm-rev::before, .chip.bm-fail::before,
.chip.bm-part::before, .chip.bm-skip::before, .chip.bm-na::before, .chip.bm-pending::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto;
  background: var(--sig, var(--muted));
}
/* HOLLOW dot (non-colour redundancy): Beta + Semantic-match read as a ring. */
.badge.beta::before, .badge.semantic::before {
  background: transparent; box-shadow: inset 0 0 0 1.6px var(--sig);
}
/* SQUARED dot: Paused differs from Review (both --warn) by SHAPE, not colour. */
.badge.paused::before { border-radius: 2px; }
/* LIVE PULSE — one expanding ring over the dot, on the "the machine is working"
   / "reconnect needed" states. Absolutely positioned so it stays concentric with
   the left-edge dot (the first flex item) instead of trailing the label. */
.badge.run::after, .badge.reauth::after, .chip.bm-run::after {
  content: ""; position: absolute; left: 0; top: 50%; margin-top: -4px;
  width: 8px; height: 8px; border-radius: 50%; box-sizing: border-box;
  border: 1.5px solid var(--sig); opacity: .6; pointer-events: none;
  animation: sig-pulse 1.7s ease-out infinite;
}
@keyframes sig-pulse {
  0%   { transform: scale(.6);  opacity: .6; }
  100% { transform: scale(2.3); opacity: 0; }
}
/* Reduced-motion degrade (belt-and-suspenders over the app-wide reduce block at
   the `prefers-reduced-motion` rule earlier in this file, which already forces
   animation-iteration-count:1 on *::after). Here we make the intent explicit and
   testable: the pulse ring is removed entirely and the static dot alone carries
   the state, so no information depends on motion. */
@media (prefers-reduced-motion: reduce) {
  .badge.run::after, .badge.reauth::after, .chip.bm-run::after {
    animation: none; opacity: 0;
  }
}
/* Narrow-width collapse (dashboard / projects / jobs status cells — the tight
   surfaces from the plate). Below 600px the uppercase label drops (font-size:0
   keeps it in the a11y tree while hiding it visually) and the fixed-px dot + its
   pulse remain the signal; the full text stays available via each badge's title=
   / aria-label. Scoped to `td.status-cell` (a marker added to just those status
   cells) so no label-hiding leaks app-wide. */
@media (max-width: 600px) {
  td.status-cell .badge { font-size: 0; gap: 0; }
}

/* ============================================================================
   Workflow Studio — Wave B AIDS RAIL (/workflows/new). A second pane of
   click-to-insert intelligence beside the create flow. EVERYTHING is scoped
   under `.wf-studio` (the two-pane wrapper) so none of this leaks onto other
   pages (the .steplist / .wf-create scoping precedent). TOKENS ONLY — every
   colour/space/radius reads an existing custom prop, so all skins + light
   inherit with no new :root token. APPEND-ONLY.
   ============================================================================ */
.wf-studio {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 350px;
  gap: var(--space-5);
  align-items: start;
}
.wf-studio-main { min-width: 0; }
/* Stack the rail under the create flow on narrower viewports. */
@media (max-width: 980px) {
  .wf-studio { grid-template-columns: 1fr; }
}

.wf-studio-rail { min-width: 0; display: flex; flex-direction: column; gap: var(--space-4); }
.wf-studio-rail .wf-rail-lead { margin: 0; }

.wf-rail-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-4);
}
.wf-rail-h {
  font-family: var(--font-display, inherit);
  font-size: 1rem; margin: 0 0 var(--space-2);
  display: flex; align-items: center; gap: var(--space-2);
}
.wf-rail-sub { margin: 0 0 var(--space-3); }

.wf-rail-list { list-style: none; margin: 0; padding: 0; display: flex;
  flex-direction: column; gap: var(--space-2); }

/* A click-to-insert control: full-width, left-aligned, ghost, hover -> terracotta
   border (the DESIGN.md secondary-button hover rule). */
.wf-ins {
  display: block; width: 100%; text-align: left; cursor: pointer;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 10px; box-shadow: none;
}
.wf-ins:hover { border-color: var(--primary); filter: none; }
.wf-ins .wf-ins-name { display: block; font-weight: 600; color: var(--text); }
.wf-ins .wf-ins-preview {
  display: block; margin-top: 2px; color: var(--muted);
  font-size: .78rem; line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wf-shape .wf-ins-preview { white-space: normal; }

.wf-ins-item { min-width: 0; }
.wf-ins-full-wrap { margin-top: 2px; }
.wf-ins-full-wrap > summary { cursor: pointer; padding: 2px 0; }
.wf-ins-full {
  margin: 4px 0 0; padding: var(--space-2);
  background: var(--bg-2, var(--panel-2)); border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  font-family: var(--mono, monospace); font-size: .72rem; line-height: 1.4;
  color: var(--text-dim); white-space: pre-wrap; word-break: break-word;
}

/* Zero-JS "Insert lines (copy)" block: the exact insert sentences as selectable
   plain text (pairs / glossary terms / style guides whose buttons show a shorthand). */
.wf-ins-lines { list-style: none; margin: 4px 0 0; padding: var(--space-2);
  background: var(--bg-2, var(--panel-2)); border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm); }
.wf-ins-lines li { font-size: .74rem; line-height: 1.5; color: var(--text-dim);
  word-break: break-word; }

/* Placeholder + pair chips: compact pill buttons that wrap. */
.wf-chip-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.wf-chip {
  display: inline-block; width: auto; cursor: pointer;
  background: var(--panel-2); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--radius-pill, 999px);
  padding: 3px 10px; font-family: var(--data-font, var(--mono, monospace));
  font-size: .76rem; box-shadow: none;
}
.wf-chip:hover { border-color: var(--primary); filter: none; }

/* "What we already know" panel. */
.wf-know-picker { display: flex; align-items: flex-end; gap: var(--space-2);
  margin-bottom: var(--space-3); }
.wf-know-picker label { flex: 1 1 auto; min-width: 0; }
.wf-know-sec { margin-top: var(--space-3); }
.wf-know-h {
  font-size: .72rem; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 var(--space-2);
}
.wf-know-name { font-weight: 600; color: var(--text); margin-right: 6px; }
.wf-know-tm { margin: 0; color: var(--text); }

.wf-know-gloss { border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 6px 8px; margin-bottom: var(--space-2); }
.wf-know-gloss > summary { cursor: pointer; display: flex; flex-wrap: wrap;
  align-items: baseline; gap: 6px; }
.wf-know-terms { list-style: none; margin: var(--space-2) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px; }
.wf-know-terms .wf-term { font-size: .78rem; padding: 5px 8px; }
.wf-term-src { font-weight: 600; }
.wf-term-tgt { color: var(--primary-2, var(--primary)); }

.wf-know-ctx { list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px; }
.wf-know-ctx li { line-height: 1.4; }

/* ============================================================================
   Workflow step-header hierarchy (Brent's P1 pain: scrolling a LONG custom
   workflow, he loses track of where one step ends and the next begins). Makes
   each step's header unmistakable while scrolling -- a bigger/bolder Headline-
   role step number, a quiet step-type chip beside it, and a clear rule/spacing
   break separating one step from the next. The rest of each step's form is
   untouched.
   Scoped two ways, both following the existing scoping precedents in this
   file: (a) `.steplist .step-head` -- workflow_builder.html's read-view step
   list; `.step-head` is a bare class ALSO used unscoped by run_view.html (see
   the "leak lesson" note above the ARC/gantt rules), so everything here is
   qualified under `.steplist`, run_view's stable per-step ancestor is absent
   there, so it is untouched. (b) `.wf-step-fieldset` / `.wf-step-legend` --
   brand-new class names added only to workflow_author_review.html's per-step
   fieldset/legend, so no ancestor qualifier is needed (nothing else in the app
   can ever carry them). `.wf-step-num` is shared verbatim by both surfaces (one
   visual language for "this is a step boundary"). TOKENS ONLY -- no new colour
   pairs; reuses --text/--border/--border-soft/--font-display/--space-*, so
   every skin x theme combination inherits with zero new contrast-check entries.
   APPEND-ONLY.

   Sticky positioning was tried for the per-step header and DELIBERATELY
   DROPPED. A <legend> that receives a non-static `position` stops being laid
   out as a fieldset legend (it loses the border-straddling notch and is
   treated as a plain block by the fieldset layout algorithm), which is an
   unreliable, engine-inconsistent trade purely to gain stickiness. Worse, the
   author-review page's per-step fieldsets vary hugely in height (data-types
   field-hiding means an lqa/human_review step is a fraction of an llm_agent
   step's height), so a pinned header for a short step would still be sitting
   over the START of the NEXT step's content on a fast scroll -- the classic
   stacked-sticky-header overlap glitch, and exactly the kind of thing the spec
   said to skip rather than ship janky. A strong static header (size, weight,
   rule, spacing) resolves the "lose my place" complaint without that risk.
   ============================================================================ */
.wf-step-num {
  font-family: var(--font-display);
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--text);
}

/* -- workflow_builder.html: the read-view step list -- */
.steplist > li.step {
  border-top: 2px solid var(--border);
  margin: var(--space-4) 0;
  padding-top: var(--space-3);
}
.steplist > li.step:first-child { margin-top: var(--space-2); }
.steplist .step-head {
  border-bottom: 1px solid var(--border-soft);
  padding-bottom: var(--space-2);
  margin-bottom: var(--space-2);
}
.steplist .step-head .wf-step-num { margin-right: 2px; }

/* -- workflow_author_review.html: the full per-step edit form -- */
.wf-step-fieldset {
  border-top: 3px solid var(--border);
  margin-top: var(--space-5);
  padding-top: var(--space-4);
}
.wf-step-fieldset:first-of-type { margin-top: 10px; }
.wf-step-legend {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: 0 8px;
}
.wf-step-type-chip { font-family: var(--data-font, var(--mono, monospace)); }

/* ============================================================
   ASK + DRAFT (SPEC-ask-draft) -- the "?" module-guide link, the grounded
   "Ask about this page" panel, and the pre-filled Draft forms. All selectors
   are namespaced (.help-*, .assist-*) so nothing here can leak app-wide.
   ============================================================ */
.help-hint {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35em;
  height: 1.35em;
  margin-left: var(--space-2);
  border: 1px solid var(--border);
  border-radius: 50%;
  font-size: 0.6em;
  font-weight: 700;
  line-height: 1;
  color: var(--muted);
  text-decoration: none;
  vertical-align: middle;
}
.help-hint:hover { color: var(--fg); border-color: var(--muted); }

.assist-panel .assist-starters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}
.assist-panel .assist-starters .chip {
  cursor: pointer;
  border: 1px solid var(--border);
  background: transparent;
  color: inherit;
}
.assist-panel .assist-answer:empty { display: none; }
.assist-panel .assist-answer {
  margin-top: var(--space-3);
  padding: var(--space-3);
  border-left: 3px solid var(--border);
}
.assist-panel .assist-answer p { margin: 0 0 var(--space-2); }
.assist-panel .assist-answer p:last-child { margin-bottom: 0; }
.assist-panel .assist-answer .assist-list { margin: 0 0 var(--space-2) 1.2em; }
.assist-panel.is-busy .assist-answer { opacity: 0.6; }
.assist-panel .grow { flex: 1 1 240px; }
/* F8: the keys-grid rail's content box is ~230px, narrower than the assist
   panel's usual 240px flex-basis for `.grow` -- scoped to the rail only so
   every other page keeps the wider default. */
.keys-rail .assist-panel .grow { flex: 1 1 auto; min-width: 0; }

.assist-draft-note {
  border-top: 2px solid var(--border);
  margin-top: var(--space-4);
  padding-top: var(--space-3);
}
.assist-degraded {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.85em;
  opacity: 0.8;
}
.warn-note { color: var(--muted); margin: var(--space-2) 0; }
.checkline { display: block; }

.help-card-dl dt { font-weight: 600; margin-top: var(--space-3); }
.help-card-dl dd { margin: 0 0 0 0; }
.help-card-index { margin: 0; padding-left: 1.2em; }
.help-card-index li { margin-bottom: var(--space-2); }
.help-card-body h2:first-child { margin-top: 0; }
