/* ControlBridge — the Control Operations Desk.
 *
 * Vanilla CSS, one file, no build step. This is the whole visual
 * system: [DESIGN.md](../../devdocs/design/DESIGN.md) is the
 * contract and this file is its implementation. §5's palette,
 * §6's type scale and §7's spacing/shape/elevation/density are
 * *checked* against this file by
 * [design_tokens_test.py](../server/design_tokens_test.py), so a
 * value that disagrees with the doc is a failing test, not a
 * matter of taste.
 *
 * Three rules that shape everything below, all from §4:
 *
 *   1. **Flat.** Content surfaces are separated by hairline
 *      borders, never by shadow. A shadow means "this floats
 *      above the workspace" — a drawer, a dialog, a menu, a
 *      popover — and nothing else may claim it.
 *   2. **No gradients, no emoji, no decorative AI styling.**
 *      Icons come from the Lucide registry
 *      ([core/icons.js](core/icons.js)) and nowhere else.
 *   3. **Colour never carries meaning alone.** Every status has
 *      words; the tint reinforces the sentence, it does not
 *      replace it.
 *
 * All colour lives in the token block below. If you are adding a
 * colour literal to a rule, stop: add or reuse a token instead.
 * See devdocs/design/brand-tokens.md. */

/* ===================== DESIGN TOKENS =====================
   Source of truth: DESIGN.md §5 (colour), §6 (type), §7
   (spacing/shape/elevation). brand-tokens.md records *why* these
   values — their provenance in ControlBridge's own UI_STANDARDS,
   the two accessibility deviations, and the rules for changing
   them.

   The LEGACY alias layer (--ink/--accent/--good/…) is gone as of
   2026-07-28. It existed so one restyle did not have to rewrite
   260 call sites; this restyle rewrote them, and keeping a second
   vocabulary alive for a stylesheet that no longer speaks it is
   how two names for one colour drift apart. */
:root {
  /* ---- Palette (DESIGN.md §5) ---- */
  --brand: #2563eb;
  --brand-dark: #1d4ed8;
  --brand-tint: #eff6ff;
  /* The LOGO's blue, not the interface's — the two are different
     colours on purpose (ADR-093) and this is the one the vendor
     marks are actually drawn in. It exists so the sign-in card
     can speak the brand's palette beside the brand's lockup
     instead of putting #2563eb next to #0061fe, which reads as a
     mistake rather than as a decision.

     STRICTLY for brand expression on the pre-auth card. It is
     NOT an interface accent: §5 reserves blue for actions,
     links, focus and selection, and that reservation is what
     stops a page turning into four competing blues. 5.07:1 on
     --surface, so it clears AA as text. */
  --brand-mark: #0061fe;
  /* The tint's own border. --line reads as a divider against
     white and disappears against --brand-tint, so a selected row
     or an informational callout had no edge at all. */
  --brand-line: #bfdbfe;
  --nav-bg: #0f172a;
  --nav-text: #f8fafc;
  --danger: #ef4444;
  --success: #16a34a;
  --warn: #f59e0b;
  --info: #2563eb;
  --text-primary: #0f172a;
  /* The standards say #64748b. Darkened by one notch, and only
     for a measured reason: #64748b clears AA on WHITE (4.76) but
     lands at 4.34 on --background #f1f5f9 — and --background, not
     white, is what sits behind most secondary text (page subtitles,
     breadcrumbs, field labels). It also failed on --warn-tint
     (4.32). #5a6a7e is the same slate hue and clears 4.5 on
     white, --background, --surface-subtle and all four tints
     (worst case 4.54 on --danger-tint). Found by measuring the
     rendered DOM, not the token table — the table looked fine.
     Held there by design_tokens_test.py, which recomputes every
     ground rather than the ones text happens to sit on today. */
  --text-secondary: #5a6a7e;
  /* Placeholder and disabled only — 2.56 on white, which WCAG
     exempts for disabled controls. Never use it for content. */
  --text-muted: #94a3b8;

  /* ---- AA-safe variants of the three semantic hues ----
     The standards' --danger/--success/--warn are correct as FILLS
     but fail WCAG 2.2 AA as small text on white: 3.76, 3.30 and
     2.15 against a 4.5 requirement. This project makes AA a
     per-view acceptance criterion, so each hue gets a darkened
     `-ink` for text and a `-tint` for its background. Same hue,
     legible: every -ink clears 4.5 on BOTH white and its own tint.
     The base hues stay untouched for fills, borders and icons. */
  --danger-ink: #ca2525;   --danger-tint: #fce3e3;
  --success-ink: #1f8143;  --success-tint: #e4fbed;
  --warn-ink: #966513;     --warn-tint: #fcf3e3;
  /* Tint borders, for the same reason as --brand-line: a pill or
     a callout needs an edge its own tint does not swallow. */
  --danger-line: #efc6c6;
  --success-line: #b8e8ca;
  --warn-line: #eedcae;

  /* ---- Neutrals ---- */
  --background: #f1f5f9;
  --surface: #ffffff;
  --surface-subtle: #f8fafc;
  --line: #e2e8f0;
  /* Emphasised boundary / control border (DESIGN.md §5).
     Distinct from --line: this is the one a control's own
     edge uses, so an input reads as interactive against a
     surface that --line merely divides. */
  --line-strong: #cbd5e1;

  /* Domain vocabulary, not diagram decoration — risk, control
   * coverage and its absence. Ported from Original so a marker
   * means to a practitioner what it meant there (ADR-126). They
   * are HERE rather than in the diagram layer because that layer
   * aliases this palette and never declares its own values: a hex
   * there has already drifted twice, once reintroducing a colour
   * this palette had rejected for failing AA. */
  --marker-risk: #b91c1c;
  /* 4.15 against white — BELOW AA's 4.5 — and legitimate only
   * because its glyph is a `<path>`, a graphical object held to
   * the 3:1 non-text threshold. Redraw that tick as a text "✓"
   * and this value drops below AA. `design_tokens_test.py`
   * recomputes ratios, so it would go red at the redraw rather
   * than here — which is correct, and is exactly why the reason
   * has to live at the value. A green suite otherwise reads as
   * "the palette is fine". */
  --marker-control-key: #e0443a;
  --marker-control-automated: #15803d;
  --marker-control-gap: #b45309;

  /* The Copilot's assistant bubble. Neutral on purpose: the other
     three bubbles carry meaning in their colour (you = brand
     fill, tool = warn tint, error = danger tint); the assistant's
     turn is the ordinary case and DESIGN.md §4 forbids
     "AI everywhere" styling, so it takes the plain grouped
     surface. */
  --msg-ai: var(--surface-subtle);

  /* ---- Nav surface. Every colour here is nav-specific:
          --text-* are tuned for light grounds and are unreadable
          on #0f172a, which is why the nav carries its own small
          set rather than borrowing the page's. ---- */
  --nav-muted: #94a3b8;
  --nav-hover: #1e293b;
  --nav-sel-bg: #1e3a8a;
  --nav-sel-ink: #ffffff;
  --nav-item-ink: #cbd5e1;
  --nav-link: #93c5fd;
  --nav-rule: #334155;

  /* ---- Shell geometry (DESIGN.md §3) ----
     Set as tokens because three things move them: the collapse
     toggle, the Copilot's resize, and the breakpoints. A grid
     template written out per state would be four templates to
     keep in step. */
  --topbar-h: 56px;
  --nav-w: 224px;
  --nav-w-rail: 56px;
  --copilot-rail: 44px;
  --copilot-w: 384px;
  --gutter: 24px;

  /* ---- Shape (§7): 6px controls, 8px panels, no more ---- */
  --radius: 6px;
  --radius-panel: 8px;

  /* ---- Elevation (§7). ONE shadow, for things that physically
          overlay the workspace. Content is flat. ---- */
  --shadow-overlay: 0 8px 24px rgba(15, 23, 42, .16);

  /* ---- Motion (§12). 80 feedback, 160 selection, 240 panel. -- */
  --t-fast: 80ms;
  --t-mid: 160ms;
  --t-slow: 240ms;

  /* UI_STANDARDS "Typography": system stack, NO external font
     load. The previous stack led with 'ITC Avant Garde Gothic',
     which has no @font-face anywhere in this repo — so it
     rendered only for people who happened to have that licensed
     face installed, and everyone else silently got a different
     typeface. */
  --font-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;
  --font-mono: SFMono-Regular, Consolas, 'Liberation Mono', Menlo,
    monospace;
}

* { box-sizing: border-box; }

/* The `hidden` ATTRIBUTE must always win.
 *
 * The browser's own `[hidden] { display: none }` is specificity
 * (0,1,0), so any rule of ours that sets `display` and names an
 * element plus a class — `button.btn`, `.card`, `.pane` — beats
 * it and the element stays on screen. That is not hypothetical:
 * the moment buttons gained `display: inline-flex`, the workflow
 * bar showed Run, Resume, Restart, Stop AND Kill at once, because
 * `wfControls()` hides four of them with `.hidden = true`.
 *
 * It matters beyond looking wrong. `hidden` is this app's chosen
 * way to hide things that must ALSO be hidden when the stylesheet
 * is removed — the global search's filtered-out `.card`s, the
 * workspace pane a narrow viewport is not showing, the run-detail
 * holder. view_check.py reads every view twice and compares; a
 * `hidden` that only works sometimes breaks that check in the
 * direction nobody notices, by showing MORE in the styled read. */
[hidden] { display: none !important; }

html { color-scheme: light; }
html, body { margin: 0; height: 100%; }
body {
  color: var(--text-primary);
  font: 14px/1.45 var(--font-sans);
  background: var(--background);
}
/* The boot curtain. `cb-booting` is set by an inline script in
   index.html before first paint and cleared by main.js once
   /api/auth/me has answered (core/identity.js `cbBootDone`).
   Without it the browser paints the static shell immediately and
   a signed-out visitor watches the topbar and sidebar for the
   length of a round-trip before the sign-in card covers them.

   `visibility`, not `display: none`: the layout is still
   computed, so revealing costs no reflow and nothing jumps. The
   sign-in overlay is deliberately NOT listed — it is the one
   thing that may paint during boot. body keeps --background, so
   the curtain is the app's own colour and not a white flash. */
.cb-booting .topbar,
.cb-booting .layout,
.cb-booting .skip-link,
.cb-booting .nav-scrim { visibility: hidden; }

button, input, select, textarea { font: inherit; }
a { color: var(--brand-dark); text-decoration: none; }
a:hover { text-decoration: underline; }
svg { display: block; }

/* ONE focus indicator for the whole application (§14). Named
   once here so no component has to remember it, and so removing
   it anywhere is a visible deletion rather than an omission. */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 1px;
}

/* Lucide icons (core/icons.js). One size and stroke for the whole
   app — a set reads as a set only if nothing varies. 16px on a
   24-unit grid, and stroke-width scaled to 1.75 because lucide's
   default 2 goes chunky below 20px. `currentColor` is inherited,
   so hover and selected states recolour the icon with no second
   asset. */
.ico-svg {
  width: 16px; height: 16px; display: block;
  stroke-width: 1.75; flex: none;
}
.ico {
  display: inline-flex; align-items: center;
  justify-content: center; flex: none;
}

/* Copilot highlight: mark the target the agent is pointing at.
   A solid ring, not a glow or a pulse — §4 forbids glowing
   borders and §12 forbids pulsing status. It reads as
   "this one", which is the whole job. */
.ui-glow {
  outline: 3px solid var(--brand) !important;
  outline-offset: 2px;
  border-radius: var(--radius);
}

/* ===================== UTILITY BAR =====================
   §3: 56px, search + context + account. `.topbar` and
   `.topbar button` are agent targets (cb_adapter SEL) — the
   class name is a contract, not a description. */
/* --tb-pad and --tb-gap are declared HERE, not in :root, and they
   have exactly ONE reader each: this rule's own `padding` and
   `gap`, plus the 719px block that overrides the two properties
   instead of restating both declarations. That is a thin reason
   to keep them and it is the whole reason — two custom properties
   are shorter than two repeated declarations, and no :root token
   means no DESIGN.md §5/§6 row (`design_tokens_test.py`'s
   `declared_tokens()` reads all scopes, and its only
   DESIGN.md-facing check runs DESIGN.md -> css, never the
   reverse). `--tint` is the existing precedent.

   **THE SECOND READER IS GONE.** Until ADR-154 `.brand` was a
   .topbar DESCENDANT that inherited both and bound its width to
   them, and this block warned that an override moving `padding`
   without moving the property would leave the mark aligned to a
   column edge that had shifted. ADR-154 deleted that width, so
   the hazard has no victim: there is nothing left to mis-align.
   The warning is removed rather than left standing over a
   deletion it no longer describes.

   20px, not 16px, and only because vagent's bar is 20px
   (ADR-154). */
.topbar {
  --tb-pad: 20px; --tb-gap: 12px;
  display: flex; align-items: center; gap: var(--tb-gap);
  height: var(--topbar-h); padding: 0 var(--tb-pad);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
/* THE MARK SITS TOP-LEFT, AT VAGENT'S GEOMETRY. Product-owner
   decision, 2026-09-09: "the ControlBridge logo on upper left of
   page should have same position and size as the logo in VAgent."
   Measured from that repo, read-only with permission:
   `#top-bar { padding: 10px 20px; display: flex;
   align-items: center }` and `#top-left #brand-logo { height:
   24px; width: auto; display: block }`. So the mark is hard left
   at --tb-pad, vertically centred, 24px tall — and --tb-pad is
   20px above to match, which is the only reason that number
   moved from 16.

   THIS REVERSES BACKLOG 22, DELIBERATELY. From 2026-08-30 the
   mark was right-justified into a box derived from --nav-w so
   its right edge landed on the sidebar's, and the whole of that
   tie is gone: no width, no justify-content, no `:has()` release
   for the rail and the drawer, and .brand no longer reads
   --nav-w at all. The three release rules that used to sit here
   are deleted rather than left inert, because a `:has()` rule
   guarding a width that is now `auto` is a rule whose absence
   changes nothing and whose presence says otherwise.

   `--nav-w` is still written to `:root` by shell.js and that is
   still load-bearing — for `.layout.nav-rail`'s override, not
   for this. The reason recorded there is now the only reason.

   ONE THING THE TIE WAS PAYING FOR IS ALSO GONE: the 20px height
   was a ceiling, not a choice, because the old 160px budget could
   not fit 24px (177.6px at the file's 7.4011:1). With no budget
   there is no ceiling, which is what made vagent's 24px
   adoptable without a second decision. */
/* `min-width: 0` only. The `display: flex; align-items: center;
   gap: 10px` that used to sit here was measured to change
   nothing: .brand has exactly one child, .topbar already centres
   its items, and the img is `display: block`. A `gap` naming a
   second child that does not exist is an invitation to add one.
   vagent's `#top-left`, which this matches, is a bare <div>. */
.brand { min-width: 0; }
.brand-logo { height: 24px; width: auto; display: block; }
/* A BUTTON WEARING A FIELD'S CLOTHES. It opens the palette; the
   real input is in there. `font: inherit` and `text-align: left`
   because a button brings the OS control font and centres its
   text, and neither is what the topbar wants. */
.search {
  flex: 1; display: flex; align-items: center; gap: 8px;
  max-width: 560px; min-width: 0;
  height: 36px; padding: 0 8px 0 11px;
  background: var(--surface-subtle);
  border: 1px solid var(--line); border-radius: var(--radius);
  color: var(--text-secondary);
  font: inherit; text-align: left; cursor: pointer;
}
.search:hover {
  border-color: var(--line-strong); background: var(--surface);
}
/* A real outline, never a border colour: finding #6 of the
   2026-07-27 guidelines audit was `outline: none` replaced by a
   colour change, which is invisible to a colour-blind user and
   is what scripts/keyboard_check.py measures rather than trusts.
   `:focus-visible` and not `:focus-within` — the control is one
   element now, and there is nothing inside it to focus. */
.search:focus-visible {
  border-color: var(--brand); background: var(--surface);
  outline: 2px solid var(--brand); outline-offset: 1px;
}
.search-text {
  flex: 1; min-width: 0; font-size: 13px;
  color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The shortcut, shown because a palette nobody knows about is a
   palette nobody opens. Hidden below 720px, where there is no
   keyboard to press it with and the room is needed. */
.search-key {
  flex: none; font: inherit; font-size: 11px;
  color: var(--text-secondary);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 4px; padding: 1px 5px;
}
.topbar-right {
  display: flex; align-items: center; gap: 8px;
  margin-left: auto;
}
/* ---- The account control: initials in a circle ---------------
   Replaced a button whose visible text was the session's ROLES
   ("control_owner, program_manager") and whose only action was an
   immediate sign-out — a destructive act one click from the corner
   of every page, wearing a label that named neither itself nor its
   consequence.

   Now: the person's initials, opening a menu with their profile,
   their settings and sign-out. The tint is HASHED from the identity
   so the circle is recognisable rather than decorative, and stable
   across reloads. `hsl` with a computed hue is the one place in this
   stylesheet a colour is not a token — a per-person colour cannot
   be, and it is deliberately low-saturation so it reads as a
   surface rather than a status. */
.acct-wrap { position: relative; }
.acct {
  width: 32px; height: 32px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid transparent; border-radius: 50%;
  background: none; color: var(--text-secondary);
  font-size: 12px; font-weight: 700; cursor: pointer;
  letter-spacing: .02em;
}
.acct.has-user {
  background: hsl(var(--tint, 220deg) 42% 92%);
  color: hsl(var(--tint, 220deg) 45% 30%);
  border-color: hsl(var(--tint, 220deg) 38% 82%);
}
.acct:hover { color: var(--text-primary); background: var(--surface-subtle); }
.acct.has-user:hover { border-color: var(--brand); }
/* Not `has-user` yet: the signed-out state is the word "Sign in",
   which needs a box rather than a circle. */
.acct:not(.has-user) {
  width: auto; height: 30px; padding: 0 9px;
  border-radius: var(--radius); font-weight: 400;
}

.acct-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 40;
  min-width: 224px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-panel); box-shadow: var(--shadow-overlay);
  display: grid; gap: 1px;
}
.acct-menu[hidden] { display: none; }
.acct-who {
  margin: 0; padding: 6px 10px 0;
  font-size: 13px; font-weight: 700; color: var(--text-primary);
}
.acct-roles {
  margin: 0; padding: 0 10px 6px; font-size: 12px;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--line); margin-bottom: 5px;
}
.acct-link {
  display: flex; align-items: center; min-height: 40px;
  padding: 8px 10px; border: 0; border-radius: var(--radius);
  background: none; color: var(--text-primary);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer;
  width: 100%;
}
.acct-link:hover {
  background: var(--surface-subtle); text-decoration: none;
}
/* Sign out is the one destructive item, so it sits below a rule and
   takes the danger ink on hover — not by default, which would make
   the menu look like a warning every time it opened. */
.acct-out {
  border-top: 1px solid var(--line); border-radius: 0 0
  var(--radius) var(--radius); margin-top: 5px;
}
.acct-out:hover { color: var(--danger-ink); background: var(--danger-tint); }

/* An icon-only control. Every one of these carries an
   aria-label — §14, and there is a browser check. */
.icon-btn {
  display: inline-flex; align-items: center;
  justify-content: center; gap: 7px;
  height: 36px; min-width: 36px; padding: 0 8px;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text-secondary);
  cursor: pointer; transition: color var(--t-fast) ease-out,
    border-color var(--t-fast) ease-out;
}
.icon-btn:hover { color: var(--brand-dark); border-color: var(--brand); }
.icon-btn.quiet { border-color: transparent; background: none; }
.icon-btn.quiet:hover { background: var(--surface-subtle); }

/* ===================== LAYOUT =====================
   Three columns whose widths are tokens, so the collapse toggle,
   the Copilot resize and every breakpoint move the same two
   numbers instead of rewriting the template. */
.layout {
  display: grid;
  grid-template-columns:
    var(--nav-w) minmax(0, 1fr) var(--copilot-rail);
  height: calc(100vh - var(--topbar-h));
  transition: grid-template-columns var(--t-mid) ease-out;
}
/* The nav's two desktop states (§8): 224px expanded, 56px rail.
   `nav-rail`, not `nav-collapsed`: the rail is not a collapse,
   it is a second legible state, and the old name went with a
   template that set the column to 0 and hid the navigation
   outright.

   **Two classes, one presentation.** `nav-rail` is the user's
   stored preference; `nav-rail-auto` is the same state forced by
   a viewport between 720 and 1279px, where §11 prefers the icon
   navigation. They are separate so the forced one can be added
   and removed without touching the preference (§11: shell
   preferences degrade, they are not overwritten) — and they share
   every rule below, because a rail that is 56px wide while still
   painting 224px of group labels is not a state, it is a bug.
   That was the bug: the width was in a media query and the
   presentation was on the class. */
.layout.nav-rail, .layout.nav-rail-auto { --nav-w: var(--nav-w-rail); }
/* THE NAVIGATION'S SEPARATOR (§8, §9). The Copilot's control on
   the other edge, and the desktop replacement for the collapse
   button — see the note beside it in index.html.

   `position: fixed`, and the alternatives were both wrong.
   `.sidebar` scrolls and clips its descendants, which is why the
   flyout lives on <body>; `.layout` is a grid with no positioning
   context, and giving it one to hold an absolutely positioned
   child changes where every abspos descendant of a view resolves.
   Fixed at `left: var(--nav-w)` needs neither, and reads the same
   live property `.brand` does.

   The 9px hit area with a 3px visible line matches `.asst-resizer`
   exactly: the same gesture on the same shell should not have two
   sizes. `margin-left: -4px` straddles the boundary — 4px inside
   the sidebar, 5px outside — rather than hanging the whole 9px
   off one side. Not centred; 9 is odd. */
.nav-resizer {
  position: fixed; z-index: 21;
  top: var(--topbar-h); bottom: 0; left: var(--nav-w);
  margin-left: -4px;
  width: 9px; cursor: col-resize; touch-action: none;
  border: 0; background: none; padding: 0;
}
.nav-resizer::after {
  content: ""; position: absolute; inset: 0 auto 0 3px;
  width: 3px; background: transparent;
  transition: background var(--t-fast) ease-out;
}
.nav-resizer:hover::after, .nav-resizer:focus-visible::after,
.nav-resizer.dragging::after { background: var(--brand); }
.nav-resizer:focus-visible { outline: none; }
/* NO RAIL OVERRIDE IS NEEDED HERE, and the rule that used to sit
   at this spot was dead. `.nav-resizer` is a DOM CHILD of
   `.layout`, so it inherits `.layout.nav-rail`'s `--nav-w`
   directly — `position: fixed` changes the containing block that
   `left` resolves against, not custom-property inheritance.
   Measured while railed: the separator's computed `--nav-w` is
   56px and its `left` is 56px, with no rule of its own. `.brand`
   needs the `:has()` release above because it is a topbar
   SIBLING; this is the case that looks identical and is not. */
/* THE BUTTON IS A NARROW-WIDTH CONTROL NOW. Above 720px the
   separator does this and the button is gone; below it the
   sidebar is a modal drawer (§11) and a closed drawer cannot be
   dragged open, so the button is the only way in and the
   separator would point at nothing. */
@media (min-width: 720px) {
  .nav-toggle { display: none; }

  /* THE FIELD IS CENTRED ON THE VIEWPORT, not on whatever space
     was left over beside the mark. Product-owner instruction,
     2026-09-09: "any visible search bar I want centered in top
     header not right next to logo."

     THIS BLOCK IS THE RIGHT HOME for the three tracks, because
     the rule above is WHY there are three. At these widths the
     toggle is `display: none`, so it generates no box and takes
     no track, and .topbar's in-flow children are exactly
     .brand, .search and .topbar-right. Below 720px the button
     comes back, there are four children, and the flex row
     .topbar declares handles them as it did before — which is
     why this is not a change to .topbar's base rule.

     A wrapper around the mark would also have worked and was
     not used: it would put a box in index.html whose only job
     is to be a grid track, and the toggle's own media query
     already draws exactly that boundary. */
  .topbar { display: grid; }

  /* EQUAL SIDE TRACKS ARE THE MECHANISM; the FLOOR is what
     stops the mechanism eating the things beside it.

     `minmax(0, 1fr)` on the sides was the first attempt and it
     is wrong in a way only a screenshot shows. At 820px the
     middle track claims its full 560, leaving 98px a side —
     under the wordmark's 178 and under the account controls'
     width — and a 0 floor permits exactly that: the mark was
     clipped mid-word and the initials circle sat on top of the
     Search button. Both tests stayed green, because nothing in
     the suite measures overlap.

     `max-content` as the floor gives the honest degradation.
     Where there is room, both sides take an equal fr share and
     the field lands on the viewport's centre line, which is the
     requirement. Where there is not, each side floors at its own
     content, the tracks stop being equal, and the field goes
     slightly off-centre rather than through its neighbours.
     Off-centre at 820px is a compromise; a clipped wordmark is
     a defect. */
  .topbar {
    grid-template-columns:
      minmax(max-content, 1fr)
      minmax(0, 560px)
      minmax(max-content, 1fr);
  }
  .search { max-width: none; width: 100%; }

  /* `margin-left: auto` did the pushing while this was a flex
     row and is now the wrong instrument — the track already
     ends at the bar's padding, and the auto margin on top of
     `justify-self` reads as two rules fighting. */
  .topbar-right { margin-left: 0; justify-self: end; }
}

/* Copilot closed is the DEFAULT (§9) — the rail is 44px and the
   work area gets the rest. Open widens to the user's width. */
.layout.copilot-open { --copilot-rail: var(--copilot-w); }

/* ===================== PRIMARY NAVIGATION =====================
   Dark navy per §4. Structure, classes and ids are agent targets
   (cb_adapter SEL): `#sidebar`, `.nav-item`, `.nav-head`,
   `[data-view]`.

   REDESIGNED 2026-07-29 for progressive disclosure. The problem was
   not font size: every hierarchy signal was shouting at once —
   uppercase tracked headings, icons at both levels, every group
   expanded, hover underlines, chevrons, badges and selection
   backgrounds all competing, with roughly twenty rows visible. The
   fix is mostly the ACCORDION (one section open, in nav.js); the
   rules below stop the remaining signals fighting.

   Four deliberate reversals of what was here:

     * headings are TITLE CASE at 13px semibold, not uppercase at
       12px with .08em tracking. §6 permits uppercase for "short
       structural labels" — permits, not requires — and its stated
       default is sentence case. Tracked uppercase on ten headings
       is the loudest thing on the page;
     * child rows carry NO icon in this state. §8 wants icons as
       "supplemental cues"; at two levels in a 224px column they
       stop supplementing and start competing. Indentation and a
       left rule carry the hierarchy instead. The icons still exist
       and the 56px rail still draws them — see that block below;
     * 44px rows, up from 36. Dense-by-default (D-22) argued for 36
       when twenty rows were visible at once; with one section open
       the whole menu is shorter at 44 than it used to be at 36, and
       44 is the target floor §14 asks for;
     * no hover underline. `a:hover` underlines every link in the
       app, and a nav row is not a document link — it was the fourth
       treatment competing on the same element. */
.sidebar {
  background: var(--nav-bg);
  border-right: 1px solid var(--nav-hover);
  overflow-y: auto; overflow-x: hidden;
  padding: 10px 8px;
  display: flex; flex-direction: column;
}
/* 8px between sections, not a blank row. */
.nav-group + .nav-group,
.nav-group + .nav-solo, .nav-solo + .nav-group,
.nav-solo + .nav-solo { margin-top: 4px; }

/* A section heading and a solo destination are the SAME row shape.
   Home, My Work and the Agent Console are one-destination groups
   rendered as flat rows, and they must not look like a different
   species of thing from the sections above and below them. */
.nav-group > .nav-head, .nav-item.nav-solo {
  display: flex; align-items: center; gap: 10px; width: 100%;
  min-height: 44px;
  background: none; border: 0; cursor: pointer; text-align: left;
  padding: 8px 10px; border-radius: var(--radius);
  font-size: 13px; line-height: 18px; font-weight: 600;
  color: var(--nav-item-ink);
  letter-spacing: normal; text-transform: none;
}
.nav-head:hover, .nav-item.nav-solo:hover {
  color: var(--nav-text); background: var(--nav-hover);
  text-decoration: none;
}
.nav-head .lbl, .nav-item.nav-solo .lbl { flex: 1; min-width: 0; }
/* One chevron, quiet, and only where there is something to open. */
.nav-head .chev {
  color: var(--nav-muted); flex: none;
  transition: transform var(--t-mid);
}
.nav-head .chev svg { width: 16px; height: 16px; }
.nav-group:not(.collapsed) .chev { transform: rotate(90deg); }
.nav-group.collapsed .nav-children { display: none; }
/* The open section's children hang off a rule, which is the only
   thing carrying the nesting once the icons are gone. */
.nav-group:not(.collapsed) > .nav-children {
  margin: 2px 0 2px 21px; padding-left: 10px;
  border-left: 1px solid var(--nav-rule);
  display: grid; gap: 1px;
}

.nav-item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 44px;
  background: none; border: 0; cursor: pointer; text-align: left;
  padding: 7px 10px; border-radius: var(--radius);
  font-size: 14px; line-height: 20px; color: var(--nav-item-ink);
}
.nav-item .lbl { flex: 1; min-width: 0; }
.nav-item:hover {
  background: var(--nav-hover); color: var(--nav-text);
  text-decoration: none;
}
/* CHILD icons are hidden in this state and restored in the rail.
   `font-size: 0` is not used here — an icon contributes no text, so
   there is nothing to keep in the accessible tree, unlike a label. */
.nav-group .nav-children .nav-item > .ico { display: none; }

/* Selected: a tint and a 3px structural marker. §8 asks for "blue,
   text, and a structural marker" — the tint and ink are the blue,
   the weight is the text, the inset bar is the marker. No underline
   and no second background. */
.nav-item.sel {
  color: var(--nav-sel-ink); font-weight: 700;
  background: var(--nav-sel-bg);
  box-shadow: inset 3px 0 0 var(--brand);
}
/* ONE badge style, on a section and on a leaf alike: compact, low
   contrast, hard right. A section carries the sum of its children so
   a closed section still reports what is inside it. */
.nav-badge-slot { display: flex; flex: none; }
.badge {
  margin-left: auto; padding: 1px 7px; border-radius: 999px;
  background: var(--nav-hover); color: var(--nav-link);
  font-size: 12px; line-height: 16px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.nav-item.sel .badge { background: var(--brand); color: var(--nav-text); }

/* ---- The footer utilities -----------------------------------
   `margin-top: auto` floats them to the bottom of the column. The
   Agent Console is here: the product's differentiator, kept
   permanently reachable and no longer presented as the
   application's purpose. It keeps a subtle accent — the link ink
   and a slightly brighter icon — and loses the card, the underline
   and the duplicate "ControlBridge Agent" heading above it. */
.nav-foot {
  margin-top: auto; padding-top: 8px;
  border-top: 1px solid var(--nav-rule);
}
.nav-item.nav-ext { color: var(--nav-link); font-weight: 600; }
.nav-item.nav-ext:hover {
  background: var(--nav-hover); color: var(--nav-text);
}
.nav-foot .nav-item > .ico { display: flex; }

/* ---- The 56px icon rail (§8) --------------------------------
   Labels are hidden VISUALLY and restored on hover and keyboard
   focus as a flyout. `font-size: 0` rather than `display: none`
   on the label span: the text stays in the accessible tree and
   in the element's textContent, which is what cb_adapter reads
   to name an agent target. Collapsing the rail must not change
   what the agent can reach. */
.layout.nav-rail .sidebar,
.layout.nav-rail-auto .sidebar { padding-inline: 6px; }
.layout.nav-rail .nav-head,
.layout.nav-rail-auto .nav-head {
  position: relative;
  /* A 1px rule cannot be hovered. The BOX stays 12px so the
     name is reachable; `background-clip: content-box` with
     symmetric padding paints only the middle 1px, so it still
     reads as a hairline. `overflow: visible` because the flyout
     escapes the box. */
  height: 12px; margin: 8px 6px; padding: 5px 0 6px;
  overflow: visible; font-size: 0;
  background: var(--nav-rule); background-clip: content-box;
}
/* The heading's own icon and chevron are hidden EXPLICITLY, not
   by clipping. This box used to be `height: 1px; overflow:
   hidden`, which hid them as a side effect — so the moment it
   grew to 12px with `overflow: visible` so the flyout could
   escape, both SVGs reappeared and the rail showed two icons per
   group with a hairline struck through them. `font-size: 0` does
   not shrink an SVG that carries its own width and height, and
   icons/icons.test.js requires that they always do (D-21). */
.layout.nav-rail .nav-head > .ico,
.layout.nav-rail-auto .nav-head > .ico { display: none; }
.layout.nav-rail .nav-group.collapsed .nav-children,
.layout.nav-rail-auto .nav-group.collapsed .nav-children { display: block; }
/* The child icons come BACK here, and this override is the whole
   reason the expanded tree can drop them: the 56px rail is an icon
   rail (§8) and children are all it has to draw. The hide rule
   above is more specific than the rail block, so restoring them
   needs saying explicitly rather than relying on order. */
.layout.nav-rail .nav-group .nav-children .nav-item > .ico,
.layout.nav-rail-auto .nav-group .nav-children .nav-item > .ico {
  display: flex;
}
.layout.nav-rail .nav-item,
.layout.nav-rail-auto .nav-item {
  position: relative;
  justify-content: center; gap: 0; padding: 7px 0; font-size: 0;
}
.layout.nav-rail .nav-item .lbl,
.layout.nav-rail-auto .nav-item .lbl { flex: none; }
/* The rail's corner badge was 9px, which is the same §6
   violation in the place hardest to read it. 12px fits: a
   two-digit count is ~14px of glyph plus 6px padding inside a
   56px rail. */
.layout.nav-rail .nav-item .badge,
.layout.nav-rail-auto .nav-item .badge {
  position: absolute; top: 0; right: 1px;
  min-width: 18px; padding: 0 4px;
  font-size: 12px; line-height: 16px; text-align: center;
}
/* The flyout label — §8 requires that a collapsed destination
   expose its full label on hover AND on keyboard focus.
   Headings get one too: a heading reduced to a hairline is
   otherwise a line between two runs of icons and which group
   you are in is a guess.

   **It is a real element on `<body>`, not a `::after`.** The
   first version was a pseudo-element positioned at
   `left: calc(100% + 8px)`, and it never appeared once: the
   `.sidebar` scrolls, so it carries `overflow-x: hidden`, and a
   scroll container clips its descendants no matter how they are
   positioned. `overflow-x: visible` is not available either — a
   box with `overflow-y: auto` computes the other axis to `auto`
   as well. So `shell.js` positions one fixed element outside the
   clip, which also keeps the label attached while the rail
   scrolls. Text is set with `textContent`, so the nav's own
   labels cannot become markup. */
.nav-flyout {
  position: fixed; z-index: 900;
  width: max-content; max-width: 220px;
  padding: 7px 9px;
  border: 1px solid var(--nav-rule); border-radius: var(--radius);
  color: var(--nav-text); background: var(--nav-bg);
  box-shadow: var(--shadow-overlay);
  font-size: 12px; font-weight: 600; line-height: 1.3;
  pointer-events: none;
  opacity: 0; transform: translate(4px, -50%);
  transition: opacity var(--t-fast) ease-out,
    transform var(--t-fast) ease-out;
}
.nav-flyout.on { opacity: 1; transform: translate(0, -50%); }

/* ===================== WORK AREA ===================== */
.main {
  overflow-y: auto;
  padding: 16px var(--gutter) 40px;
  min-width: 0;
}
.breadcrumb {
  color: var(--text-secondary); font-size: 12px;
  margin-bottom: 8px;
}
.breadcrumb b { color: var(--text-primary); font-weight: 600; }

/* ---- Page head (§3's hierarchy) -----------------------------
   Title, one-sentence state, and the primary action on the same
   line. The action belongs HERE and not at the end of the page:
   §2 puts the next action near the task. */
.page-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; margin: 0 0 16px;
}
.page-head-text { min-width: 0; }
.page-head h1 {
  margin: 0; font-size: 20px; line-height: 28px; font-weight: 700;
  letter-spacing: -.02em;
}
.page-head p {
  margin: 3px 0 0; color: var(--text-secondary); font-size: 13px;
}
.page-head-actions { display: flex; gap: 8px; flex: none; }
.page-head-actions:empty { display: none; }

/* A section inside a page. §6: 16px/24px, 700 — a heading that
   does not compete with the page title for the working space. */
.section {
  margin: 24px 0 0;
}
.section > h2 {
  margin: 0 0 8px; font-size: 16px; line-height: 24px;
  font-weight: 700;
}
.section > p.section-sub {
  margin: -4px 0 10px; color: var(--text-secondary); font-size: 13px;
}
/* A short structural label. §6 limits uppercase to 12px with
   letter spacing, and this is the only place it is used. */
.section-label {
  margin: 16px 0 6px;
  color: var(--text-secondary);
  font-size: 12px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
}

/* ===================== OPERATIONAL SUMMARY =====================
   §3: a compact strip of four or five cells, not a mosaic of
   floating cards. One bordered surface, hairline-divided; every
   value drills into the records behind it, so each cell is a
   real <a>. */
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  margin: 0 0 16px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  overflow: hidden;
}
.metric {
  display: block; padding: 10px 13px;
  border-right: 1px solid var(--line);
  color: inherit; text-decoration: none;
}
.metric:last-child { border-right: 0; }
a.metric:hover { background: var(--surface-subtle); text-decoration: none; }
a.metric:hover .metric-v { color: var(--brand-dark); }
.metric-v {
  display: block; font-size: 18px; line-height: 1.15;
  font-weight: 700; font-variant-numeric: tabular-nums;
}
.metric-l {
  display: block; margin-top: 2px;
  color: var(--text-secondary); font-size: 12px;
}
.metric.attention .metric-v { color: var(--warn-ink); }
.metric.problem .metric-v { color: var(--danger-ink); }

/* ===================== QUEUE-DETAIL WORKSPACE =====================
   §3: queue ~40%, detail ~60%, minimum 320px and 520px. One
   bordered surface with an internal divider rather than two
   floating panels — the selection has to stay visible while the
   detail is read, and two cards with a gap between them read as
   two pages. */
.workspace {
  display: grid;
  grid-template-columns: minmax(320px, .4fr) minmax(520px, .6fr);
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  overflow: hidden;
}
/* The mirror ratio, for Document Intelligence review (§10):
   results lead, source supports. Same primitive, opposite
   weighting — check which surface you are building. */
.workspace.mirrored {
  grid-template-columns: minmax(520px, .6fr) minmax(320px, .4fr);
}
/* Three panes, for account mapping (§ "Three-pane mapping
   workspace"). 40/35/25 — Original's ResizablePanelGroup ratio as
   a plain grid, because the ratio reflects how much text each
   pane carries and was never a thing a reader needed to drag.
   Below 1100px they stack, register first: it is the only one of
   the three that is useful on its own. */
.workspace.triptych {
  grid-template-columns:
    minmax(380px, .40fr) minmax(300px, .35fr) minmax(260px, .25fr);
}
@media (max-width: 1100px) {
  .workspace.triptych { grid-template-columns: 1fr; }
  .workspace.triptych .pane.queue-pane {
    border-right: 0; border-bottom: 1px solid var(--line);
  }
}
/* The register row: identifier, then name over its type and the
   two claims, then the figure. The sub-lines are blocks so the
   name keeps the column's full width — five columns in a 40%
   pane is what broke "Accounts Receivable" into four lines. */
.acct-name { display: block; font-weight: 600; }
.acct-claims { display: block; }
.workspace.triptych .tbl td { vertical-align: top; }
.workspace.triptych .tbl .num { white-space: nowrap; }
.pick { display: flex; align-items: center; gap: 8px; }
.bucket-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
/* `h2` joins the list because the heading LEVEL is decided by
   where the panel sits, not by how it looks: the reconciliations
   groups facet renders its definition panel as the subject of the
   page, so its heading is an <h2> and the groups section's <h2>
   follows it at the same level. Selecting by tag and omitting one
   level meant that panel silently lost the `margin: 0` this
   component depends on. */
.bucket-head h2, .bucket-head h3, .bucket-head h4 { margin: 0; }
.fsli-statement {
  margin: 4px 0 8px; font-size: 13px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-secondary);
}
.fsli-total { font-weight: 600; margin: 4px 0; }
/* The tie-out, inside a statement-line card. Three figures on
   one row and a sentence beneath: the sentence is load-bearing,
   because a mis-signed line shows a difference of ZERO and the
   words are all that distinguish it from a clean row. */
.tie-out {
  margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--line);
}
.tie-note { margin: 6px 0 0; font-size: 13px; }
.setline { display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap; margin: 0 0 12px; }
.pane { min-width: 0; display: flex; flex-direction: column; }
.pane.queue-pane { border-right: 1px solid var(--line); }
.pane-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-height: 48px; padding: 8px 13px;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--line);
}
/* 16/24 — §6's section-title role, the same as `.section > h2`.
   At 14/20 the app had two sizes of section heading depending on
   which primitive drew it, which is a scale with a hole in it
   rather than a smaller heading. */
.pane-head h2, .pane-head h3 {
  margin: 0; font-size: 16px; line-height: 24px; font-weight: 700;
}
.pane-body { min-width: 0; padding: 14px 16px; overflow-wrap: anywhere; }
.pane-body.flush { padding: 0; }

/* ---- One queue row (§3) ------------------------------------
   Reading order is fixed by the contract: action-oriented title,
   identifier, due state or severity in words, reason, next
   action. A <button>, so the keyboard and the agent's DOM
   adapter both reach it. */
.queue { display: block; }
.queue-row {
  display: grid;
  grid-template-columns: 4px minmax(0, 1fr) auto;
  gap: 0 11px; align-items: center;
  width: 100%; min-height: 56px;
  padding: 8px 12px 8px 0;
  background: var(--surface); border: 0;
  border-bottom: 1px solid var(--line);
  text-align: left; color: var(--text-primary);
  cursor: pointer; font: inherit;
  transition: background var(--t-mid) ease-out;
}
.queue-row:hover { background: var(--surface-subtle); }
.queue-row.sel { background: var(--brand-tint); }
.queue-row:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
/* Selection is a tint AND a marker; focus is the ring above.
   §5 keeps the two distinct on purpose. */
.queue-mark { align-self: stretch; background: transparent; }
.queue-row.sel .queue-mark { background: var(--brand); }
.queue-main { min-width: 0; }
.queue-title {
  font-weight: 700; font-size: 13px; line-height: 18px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.queue-meta {
  display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 2px;
  color: var(--text-secondary); font-size: 12px;
}
.queue-meta .id { font-variant-numeric: tabular-nums; }
.queue-next {
  align-self: center; flex: none;
  color: var(--brand-dark); font-size: 12px; font-weight: 700;
}

/* ===================== STATUS PILL =====================
   §8: a pill holds a short TEXT state. An icon or a colour may
   reinforce it; neither may replace it. */
.pill {
  display: inline-flex; align-items: center; gap: 5px;
  min-height: 22px; padding: 2px 8px; border-radius: 999px;
  border: 1px solid transparent;
  font-size: 12px; font-weight: 700; white-space: nowrap;
}
.pill.info { color: var(--brand-dark); background: var(--brand-tint);
  border-color: var(--brand-line); }
.pill.warn { color: var(--warn-ink); background: var(--warn-tint);
  border-color: var(--warn-line); }
.pill.danger { color: var(--danger-ink); background: var(--danger-tint);
  border-color: var(--danger-line); }
.pill.success { color: var(--success-ink); background: var(--success-tint);
  border-color: var(--success-line); }
.pill.neutral { color: var(--text-secondary); background: var(--surface-subtle);
  border-color: var(--line); }

/* `.tier` keeps its four class names. The standards use a
   `.pill-*` vocabulary and renaming would touch dom.js and every
   view that emits a tier — code churn dressed as styling. The
   mapping, for when it is done: eff→success, exc→danger,
   untested→warn, key→info. */
.tier {
  display: inline-flex; align-items: center;
  align-self: flex-start;
  min-height: 22px; padding: 2px 8px; border-radius: 999px;
  border: 1px solid transparent;
  font-size: 12px; font-weight: 700; white-space: nowrap;
}
.tier.eff { color: var(--success-ink); background: var(--success-tint);
  border-color: var(--success-line); }
.tier.exc { color: var(--danger-ink); background: var(--danger-tint);
  border-color: var(--danger-line); }
.tier.untested { color: var(--warn-ink); background: var(--warn-tint);
  border-color: var(--warn-line); }
.tier.key { color: var(--brand-dark); background: var(--brand-tint);
  border-color: var(--brand-line); }

/* A provenance mark (§9, §10): extracted / deterministic /
   AI-proposed / human-confirmed. Deliberately NOT colour-coded
   by "AI vs not" — §4 forbids AI-everywhere styling, and the
   distinction that matters is whether a human has confirmed it,
   which is the only one that gets the success tone. */
.prov {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface-subtle);
  color: var(--text-secondary);
  font-size: 12px; font-weight: 600; white-space: nowrap;
}
.prov.confirmed { color: var(--success-ink); background: var(--success-tint);
  border-color: var(--success-line); }
.prov.proposed { color: var(--warn-ink); background: var(--warn-tint);
  border-color: var(--warn-line); }

/* ===================== DECISION CALLOUT =====================
   §8: heading, plain-language reason, consequence or deadline,
   the primary decision action, and the way through to the basis.
   Amber rather than red by default — the record is valid and a
   person is being asked to decide, not to clear an error. */
.decision {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px; align-items: center;
  margin: 0 0 14px; padding: 12px 13px;
  background: var(--warn-tint);
  border: 1px solid var(--warn-line);
  border-left: 4px solid var(--warn);
  border-radius: var(--radius-panel);
}
.decision.blocking {
  background: var(--danger-tint); border-color: var(--danger-line);
  border-left-color: var(--danger);
}
.decision h2, .decision h3 {
  margin: 0 0 3px; font-size: 14px; line-height: 20px; font-weight: 700;
}
.decision p { margin: 0; color: var(--text-secondary); font-size: 13px; }
.decision-acts { display: flex; flex-direction: column; gap: 6px; flex: none; }

/* ===================== RECORD SUMMARY =====================
   §8: owner, due state, lifecycle state, evidence completeness,
   material identifiers — and nothing else. One bordered strip of
   labelled facts, so the label travels with its value when the
   stylesheet is gone. */
.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  margin: 0 0 4px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  overflow: hidden;
}
.fact {
  padding: 9px 11px; border-right: 1px solid var(--line);
  min-width: 0;
}
.fact:last-child { border-right: 0; }
.fact dt, .fact .k {
  display: block; color: var(--text-secondary);
  font-size: 12px; font-weight: 500;
}
.fact dd, .fact .v {
  display: block; margin: 3px 0 0;
  font-size: 13px; font-weight: 600; overflow-wrap: anywhere;
}

/* ---- The basis (§2: status, basis and action stay together) --
   A definition list, because that is what it is: the rule, the
   evidence or the event that produced the state, each named. */
.basis { margin: 0; }
.basis > div {
  display: grid; grid-template-columns: 116px minmax(0, 1fr);
  gap: 6px 14px; padding: 9px 0;
  border-top: 1px solid var(--line); font-size: 13px;
}
.basis > div:first-child { border-top: 0; }
.basis dt { color: var(--text-secondary); }
.basis dd { margin: 0; overflow-wrap: anywhere; }

/* ===================== BUTTONS =====================
   §8: one primary per decision region. Labels begin with a verb;
   the CSS cannot enforce that but the string catalogue can, and
   does. */
button.btn, a.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 7px; min-height: 36px; padding: 7px 13px;
  border: 1px solid transparent; border-radius: var(--radius);
  background: var(--brand); color: var(--surface);
  font-size: 13px; font-weight: 700; cursor: pointer;
  text-decoration: none;
  transition: background var(--t-fast) ease-out,
    border-color var(--t-fast) ease-out, color var(--t-fast) ease-out;
}
button.btn:hover, a.btn:hover {
  background: var(--brand-dark); text-decoration: none;
}
button.btn:disabled { opacity: .55; cursor: default; }
/* Secondary — equal safety, lower priority. */
button.btn.ghost, a.btn.ghost {
  background: var(--surface); color: var(--text-primary);
  border-color: var(--line-strong);
}
button.btn.ghost:hover, a.btn.ghost:hover {
  background: var(--surface-subtle); color: var(--brand-dark);
  border-color: var(--brand);
}
/* Quiet / link — inspect, reveal, navigate. */
button.btn.quiet {
  background: none; color: var(--brand-dark);
  border-color: transparent; font-weight: 700;
}
button.btn.quiet:hover { background: var(--brand-tint); }
/* Danger — destructive or materially consequential. Outlined,
   not filled: a filled red button invites the accidental press
   it exists to warn about. */
button.btn.danger {
  background: var(--surface); color: var(--danger-ink);
  border-color: var(--danger-line);
}
button.btn.danger:hover {
  background: var(--danger-tint); border-color: var(--danger);
}
button.btn.sm { min-height: 30px; padding: 4px 10px; font-size: 12px; }
button.btn.lg { min-height: 40px; padding: 9px 16px; font-size: 14px; }

/* ---- Filters and facets ------------------------------------
   §8: filtering must be predictable and the active filter must
   stay visible. The count travels inside the label so it
   survives with the stylesheet off (D-21). */
.facets, .toolbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 0 0 12px; padding: 0;
}
.pane-head .facets, .pane-head .toolbar { margin: 0; }
.facet, .chip {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 30px; padding: 4px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text-secondary);
  font-size: 12px; cursor: pointer;
}
.facet:hover, .chip:hover {
  border-color: var(--brand); color: var(--brand-dark);
}
.facet.on, .chip.on {
  color: var(--brand-dark); background: var(--brand-tint);
  border-color: var(--brand-line); font-weight: 700;
}
.facet-n { font-variant-numeric: tabular-nums; opacity: .75; }
.facet.on .facet-n { opacity: .9; }

select.filter, input.filter {
  min-height: 36px; padding: 6px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text-primary);
  font-size: 13px;
}
select.filter:hover, input.filter:hover { border-color: var(--brand); }

/* ===================== PANELS =====================
   `.panel-box` is an agent target (cb_adapter SEL). Flat: a
   border, 8px, no shadow (§7). */
.panel-box {
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  padding: 14px 16px; margin: 0 0 12px;
}
.panel-box > h3 {
  margin: 0 0 10px; font-size: 14px; line-height: 20px; font-weight: 700;
}
.panel-box > :last-child { margin-bottom: 0; }
.kv { display: grid; grid-template-columns: 150px minmax(0, 1fr);
  gap: 7px 14px; font-size: 13px; }
.kv .k { color: var(--text-secondary); }

/* ---- Record cards -------------------------------------------
   `.grid` + `.card` are agent targets: cb_adapter's DYNAMIC
   search boundary reads `#view .card`, and main.js's global
   search filters them. So the DOM stays and the LOOK changes —
   these are now full-width flat record rows in a single column,
   not the 240px mosaic §4 forbids. One column at every width, on
   purpose: a two-up grid of records is a mosaic with better
   manners. */
.grid { display: grid; gap: 0; grid-template-columns: 1fr;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  overflow: hidden; }
.grid:empty { display: none; }
.card {
  display: grid; grid-template-columns: 22px minmax(0, 1fr) auto;
  gap: 12px; align-items: center;
  padding: 11px 14px;
  background: var(--surface);
  border: 0; border-bottom: 1px solid var(--line);
}
.card:last-child { border-bottom: 0; }
.card:hover { background: var(--surface-subtle); }
.card .thumb {
  display: flex; align-items: center; justify-content: center;
  color: var(--text-secondary);
}
.card .body { min-width: 0; display: block; }
.card h3 {
  margin: 0; font-size: 13px; line-height: 18px; font-weight: 700;
  overflow-wrap: anywhere;
}
.card .sub {
  color: var(--text-secondary); font-size: 12px; margin-top: 2px;
}
.card .tier { margin: 0 0 4px; }
.card .foot { display: flex; align-items: center; gap: 8px; flex: none; }

/* ===================== FORMS =====================
   §8's Forms contract, rendered by [core/form.js](core/form.js).
   Six rules, and the ones this stylesheet is responsible for:

   * the LABEL is a block above its control and always visible —
     never a placeholder, which disappears exactly when the
     constraint is needed;
   * the HINT is permanent body text under the control, so a
     constraint or example is readable before submission and does
     not move the layout when it becomes an error;
   * an error recolours the hint IN PLACE. Nothing is inserted,
     so a field turning invalid does not shift every field below
     it — the reflow is what makes people lose their place;
   * a max width on the control, because a 900px-wide text input
     for a two-character answer reads as the wrong field.

   No new token values (§5/§6 are held to this file token by
   token by design_tokens_test.py) — every colour here is an
   existing custom property. */
.form { max-width: 720px; }
.field-group {
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  background: var(--surface); padding: 4px 16px 14px;
  margin: 0 0 12px; min-width: 0;
}
.field-group > legend {
  padding: 0 6px; font-size: 14px; line-height: 20px;
  font-weight: 700; color: var(--text-primary);
}
.group-note {
  margin: 6px 0 12px; font-size: 13px; line-height: 19px;
  color: var(--text-secondary);
}
.field-plain { min-width: 0; }
.field { margin: 0 0 14px; }
.field > label {
  display: block; font-size: 13px; line-height: 18px;
  font-weight: 600; margin-bottom: 4px;
}
.field .req { color: var(--danger-ink); }
.field input, .field select, .field textarea {
  width: 100%; max-width: 420px; box-sizing: border-box;
  font: inherit; font-size: 13px; padding: 7px 9px;
  color: var(--text-primary); background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  transition: border-color var(--t-fast) ease-out;
}
.field textarea { max-width: 640px; resize: vertical; }
.field input:hover, .field select:hover, .field textarea:hover {
  border-color: var(--brand);
}
.field input[readonly], .field textarea[readonly] {
  background: var(--surface-subtle); color: var(--text-secondary);
}
.field .hint {
  margin: 4px 0 0; font-size: 12px; line-height: 17px;
  color: var(--text-secondary);
}
/* The error IS the hint, recoloured — see the section note. */
.field .hint.err { color: var(--danger-ink); font-weight: 600; }
.field.bad input, .field.bad select, .field.bad textarea {
  border-color: var(--danger-ink);
}
/* Rule 6: a value a machine proposed must not look like one a
   person asserted. */
.field .proposed {
  display: flex; align-items: center; gap: 5px;
  margin: 5px 0 0; font-size: 12px; line-height: 17px;
  color: var(--text-secondary);
}
.field .proposed .ico { display: inline-flex; }
.field .proposed .ico svg { width: 13px; height: 13px; }
.form-errors {
  margin: 0 0 12px; padding: 10px 12px; font-size: 13px;
  line-height: 19px; font-weight: 600;
  color: var(--danger-ink); background: var(--danger-tint);
  border: 1px solid var(--danger-line);
  border-radius: var(--radius-panel);
}
.form-foot { display: flex; gap: 8px; flex-wrap: wrap; }

/* A multi-select, as checkboxes. Two columns on a wide viewport
   because ten single-file rows push the rest of the step below the
   fold — and one column below 640px, where two would each be too
   narrow to read. */
.checkgroup { border: 0; padding: 0; margin: 0 0 14px; min-width: 0; }
.checkgroup > legend {
  padding: 0; font-size: 13px; line-height: 18px; font-weight: 600;
  margin-bottom: 4px;
}
.checkgroup > .hint { margin: 0 0 8px; }
.check {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 8px; align-items: start;
  padding: 7px 9px; margin: 0 0 4px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); font-size: 13px; line-height: 18px;
  cursor: pointer; transition: border-color var(--t-fast) ease-out;
}
.check:hover { border-color: var(--brand); }
/* `accent-color` rather than a hand-drawn box: the native control
   keeps its platform focus ring and its 44px touch target.

   `width: auto` is load-bearing and not a tidy-up. A checkgroup
   also carries `.field`, so `.field input { width: 100% }` applied
   here and made every checkbox a full-width row — which pushed its
   own label onto the next line and centred the box above the text.
   The rule is right for a text input and wrong for a checkbox. */
.check input {
  accent-color: var(--brand); margin: 1px 0 0;
  width: auto; max-width: none;
}
.check-note {
  display: block; color: var(--text-secondary); font-size: 12px;
  line-height: 17px;
}
@media (min-width: 641px) {
  .checkgroup { display: grid; grid-template-columns: 1fr 1fr;
    gap: 0 10px; }
  .checkgroup > legend, .checkgroup > .hint,
  .checkgroup > .proposed { grid-column: 1 / -1; }
}

/* ===================== SETUP WIZARD =====================
   M-10 f2. The "guided discovery" the personas doc names as the
   dominant need in a first controls build.

   The rail is a real <ol> of buttons, not a decorative stepper:
   somebody who realises on step 5 that they mistyped the fiscal
   year end must not have to press Back four times. `aria-current`
   carries which step is live, so the state is not colour alone. */
.wiz-progress {
  display: flex; align-items: center; gap: 12px; margin: 0 0 10px;
}
.wiz-count {
  margin: 0; font-size: 13px; font-weight: 600; flex: none;
  color: var(--text-secondary); font-variant-numeric: tabular-nums;
}
.wiz-bar {
  flex: 1; height: 6px; min-width: 80px;
  background: var(--line); border-radius: 999px; overflow: hidden;
}
.wiz-bar > span {
  display: block; height: 100%; background: var(--brand);
  transition: width var(--t-mid) ease-out;
}
.dirty {
  margin: 0 0 12px; padding: 7px 11px;
  font-size: 12px; line-height: 17px; font-weight: 600;
  color: var(--warn-ink); background: var(--warn-tint);
  border: 1px solid var(--warn-line); border-radius: var(--radius);
}
.wiz-rail { margin: 0 0 16px; }
/* ---- The phase rail (four chapters, six screens) ------------
   Nested ordered lists: a sequence of phases each holding a
   sequence of screens, which is what this is. Every row carries its
   state as TEXT — visibly for a phase, screen-reader-only for a
   screen — so nothing here means anything by colour alone (§14).
   The left border is the only decorative signal and it is
   redundant with that text by design. */
.wiz-rail ol { list-style: none; margin: 0; padding: 0; }
.ph { padding: 0 0 4px 10px; margin: 0 0 10px;
  border-left: 3px solid var(--line); }
.ph.current { border-left-color: var(--brand); }
.ph.complete { border-left-color: var(--success); }
.ph.attention { border-left-color: var(--warn); }
.ph-head { display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 4px 8px; margin: 0 0 6px; }
.ph-n { font-size: 11px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted);
  flex: 0 0 100%; }
.ph-name { font-size: 13px; line-height: 18px; font-weight: 700; }
.ph-state { font-size: 11px; color: var(--text-secondary); }
.ph.attention .ph-state { color: var(--warn-ink); font-weight: 600; }
.ph.complete .ph-state { color: var(--success-ink); }
.ph-screens { display: grid; gap: 3px; }

/* A screen that is not yet available stays VISIBLE and disabled —
   one that vanished would hide what is still coming, and one with
   no stated reason reads as a bug. The reason is in `title` and in
   the screen-reader text. */
.wiz-step:disabled {
  opacity: .55; cursor: not-allowed; border-style: dashed;
}
.wiz-step:disabled:hover { border-color: var(--line); }

/* The compact header, below 720px: phase of how many, its name,
   this screen and its place within the phase. */
.wiz-here { display: none; margin: 0 0 12px; }
.wiz-here p { margin: 0; }
.wh-phase { font-size: 11px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted); }
.wh-name { font-size: 16px; line-height: 22px; font-weight: 700; }
.wh-screen { font-size: 13px; color: var(--text-secondary); }
.wh-within { font-variant-numeric: tabular-nums; }
.wiz-here .btn { margin-top: 8px; }
@media (min-width: 961px) {
  /* A vertical rail, so six meaningful labels cannot wrap with one
     orphaned on a second line — which is what the horizontal pills
     did at 1280px. The answer region keeps §8's readable measure. */
  .wiz-rail ol { flex-direction: column; gap: 4px; }
  .wiz-step { width: 100%; text-align: left;
    border-radius: var(--radius); }
  .wiz-cols {
    display: grid; grid-template-columns: 232px minmax(0, 1fr);
    gap: 24px; align-items: start;
  }
  .wiz-cols > .wiz-rail { margin: 0; }
  /* §8's readable measure. Without the cap the answer column grows
     to the window and a one-line label sits 900px from its field. */
  .wiz-cols > .wiz-body { max-width: 720px; }
  /* Forms keep the readable measure above. The document branch is
     a record workspace: its repeated filenames, provenance and
     filing status need the width the shell already provides. */
  .wiz-cols > .wiz-body.is-document-workspace { max-width: none; }
}
.wiz-rail ol {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.wiz-step {
  font: inherit; font-size: 12px; line-height: 17px;
  padding: 5px 10px; cursor: pointer;
  color: var(--text-secondary); background: var(--surface);
  border: 1px solid var(--line); border-radius: 999px;
  transition: border-color var(--t-fast) ease-out;
}
.wiz-step:hover { border-color: var(--brand); }
/* The four states, renamed with the phase model. These selectors
   went stale when `done`/`on` became `complete`/`current` and the
   rail rendered four identical-looking rows for a while — the
   states were correct in the DOM and invisible on screen. Colour is
   never the only signal: every row also carries its state as
   screen-reader text, and a phase shows it visibly. */
.wiz-step.complete {
  color: var(--success-ink); border-color: var(--success-line);
}
.wiz-step.attention {
  color: var(--warn-ink); border-color: var(--warn-line);
  font-weight: 600;
}
.wiz-step.current {
  color: var(--nav-sel-ink); background: var(--brand);
  border-color: var(--brand); font-weight: 700;
}
.wiz-step.todo { color: var(--text-secondary); }
/* The two paths at the fork. Equal weight in layout — the
   recommendation is carried by the copy and the button emphasis,
   not by making one card larger. */
.wiz-paths {
  display: grid; gap: 12px; grid-template-columns: 1fr;
  margin: 0 0 14px;
}
@media (min-width: 641px) {
  .wiz-paths { grid-template-columns: 1fr 1fr; }
}
/* The SEC path is a PEER of the other two but sits in its own
   grid, one column wide, because it carries an input and a
   confirmation step that the other cards do not. Side by side at
   equal width it would either be cramped or force the other two
   to match its height for content they do not have. Stacked
   above, tinted, it reads as the recommended route without
   pretending to be the same shape. */
.wiz-paths-lead { grid-template-columns: 1fr; }
@media (min-width: 641px) {
  .wiz-paths-lead { grid-template-columns: 1fr; }
}
.wiz-paths-lead .wiz-path {
  background: var(--brand-tint); border-color: var(--brand-line);
}
/* Label, input and button on one line, wrapping to stacked on a
   narrow viewport rather than overflowing. */
.row-inline {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  margin-top: auto;
}
.row-inline label { font-size: 13px; }
.inp-sm {
  width: 8em; padding: 6px 9px; font: inherit; font-size: 13px;
  color: var(--text-primary); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.inp-sm:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 1px;
}
.sec-found {
  margin-top: 10px; padding: 10px 12px; background: var(--surface);
  border: 1px solid var(--brand-line);
  border-radius: var(--radius-panel);
}
.sec-found p { margin: 0 0 6px; }
/* Column layout so the two cards' buttons sit on one line: the
   grid already stretches both cards to the taller one's height,
   and `margin-top: auto` on the button drops it to the bottom of
   each. Without it the button follows its own paragraph, so the
   longer body text pushes one button below the other. */
.wiz-path {
  background: var(--surface); padding: 14px 16px;
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  display: flex; flex-direction: column; align-items: flex-start;
}
.wiz-path h3 {
  margin: 0 0 6px; font-size: 14px; line-height: 20px; font-weight: 700;
}
.wiz-path p {
  margin: 0 0 12px; font-size: 13px; line-height: 19px;
  color: var(--text-secondary);
}
.wiz-path .btn { margin-top: auto; }

/* ============================================================
   THE DISCLOSURE — core/collapse.js
   ============================================================
   Replaces Original's Radix Accordion/Collapsible. A button and a
   region; the chevron rotates and the region is `hidden`, so
   nothing here is doing the hiding — CSS cannot leave content in
   the tab order by accident. */
.cl {
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  background: var(--surface); margin: 0 0 8px;
}
.cl-head { margin: 0; font-size: inherit; font-weight: inherit; }
.cl-trigger {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 11px 13px; border: 0; border-radius: var(--radius-panel);
  background: none; color: var(--text-primary); cursor: pointer;
  font: inherit; text-align: left;
  transition: background var(--t-fast) ease-out;
}
.cl-trigger:hover { background: var(--surface-subtle); }
.cl-title { flex: 1; font-size: 14px; font-weight: 700; }
/* The count is the reason the accordion works: "have I done this
   one?" is answerable without opening it. Tabular figures so a
   column of them does not jitter. */
.cl-count {
  font-size: 12px; color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.cl-badge {
  font-size: 11px; line-height: 16px; padding: 1px 7px;
  border-radius: 999px; border: 1px solid var(--line);
  color: var(--text-secondary); background: var(--surface-subtle);
}
.cl-badge.bad {
  color: var(--danger-ink); background: var(--danger-tint);
  border-color: var(--danger-line);
}
.cl-chev {
  display: inline-flex; color: var(--text-secondary);
  transition: transform var(--t-fast) ease-out;
}
.cl.open > .cl-head .cl-chev { transform: rotate(180deg); }
.cl-region { padding: 0 13px 13px; }

/* ============================================================
   THE MULTISELECT — core/form.js
   ============================================================
   Original's MultiSystemPicker: a summary, a searchable list, a
   write-in, and the chosen names as chips OUTSIDE the panel — the
   chips are what make the answer readable without opening it. */
.ms { display: grid; gap: 8px; }
.ms-trigger {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; width: 100%; padding: 8px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text-primary);
  font: inherit; font-size: 13px; cursor: pointer; text-align: left;
}
.ms-trigger:hover { border-color: var(--brand); }
.ms-summary { overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.ms-chev { display: inline-flex; color: var(--text-secondary); }
.ms-panel {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-subtle); padding: 9px;
}
.ms-search { display: block; margin: 0 0 8px; }
.ms-q, .ms-new {
  width: 100%; padding: 7px 9px; font: inherit; font-size: 13px;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text-primary);
}
/* Capped and scrollable: a category with a hundred and twenty
   vendors must not push the application cards below it off the
   page. This is the whole reason the kind exists — the list is too
   long to show, so `checkboxes` would not do. */
.ms-list { max-height: 232px; overflow-y: auto; }
.ms-empty {
  margin: 6px 0 0; font-size: 12px; color: var(--text-secondary);
}
.ms-add {
  display: flex; gap: 6px; align-items: center;
  margin-top: 9px; padding-top: 9px;
  border-top: 1px solid var(--line);
}
.ms-add .btn { flex-shrink: 0; }
.ms-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px; font: inherit; font-size: 12px; line-height: 18px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--surface-subtle); color: var(--text-secondary);
  cursor: pointer;
}
.chip:hover {
  color: var(--danger-ink); background: var(--danger-tint);
  border-color: var(--danger-line);
}
.chip svg { width: 12px; height: 12px; }

/* The "Other" write-in beside a select. DISABLED rather than
   hidden — see core/form.js — so it needs a disabled look rather
   than a display rule. */
.field-other { margin-top: 6px; }
.field-other:disabled {
  background: var(--surface-subtle); color: var(--text-secondary);
  cursor: not-allowed;
}

/* ============================================================
   THE APPLICATIONS SCREEN — views/company/landscape.js
   ============================================================ */
.ls-cat {
  padding: 11px 0 3px; border-top: 1px solid var(--line);
}
.ls-cat:first-of-type { border-top: 0; }
.ls-cat h4 {
  margin: 0 0 8px; font-size: 13px; line-height: 18px;
  font-weight: 700;
}
.ls-cat .form-foot { display: none; }
/* The cards and the save bar are redrawn on their own; the picker
   above them never is. That is what keeps the panel open, the
   search text intact and focus where the person put it while three
   banks are ticked — see landscape.js's showCategory. */
.ls-cards, .ls-foot { display: block; }
/* One card per application, indented so it reads as belonging to
   the category above rather than sitting beside it. */
.ls-sys {
  margin: 8px 0 0; padding: 10px 12px;
  border: 1px solid var(--line); border-left: 3px solid var(--line-strong);
  border-radius: var(--radius); background: var(--surface-subtle);
}
.ls-sys.bad {
  border-color: var(--danger-line);
  border-left-color: var(--danger);
  background: var(--danger-tint);
}
.ls-sys-name {
  margin: 0 0 8px; font-size: 13px; line-height: 18px;
  font-weight: 700;
}
.ls-save {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: 10px 0 0; padding: 9px 11px;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface);
}
.ls-save p {
  margin: 0; flex: 1; font-size: 12px; color: var(--text-secondary);
}
.ls-save .form-errors { flex-basis: 100%; }

/* ============================================================
   THE LOGO — views/company/logo.js
   ============================================================
   The monogram is ALWAYS rendered and the image sits over it, so
   an image that fails to load degrades to two initials rather
   than to an empty box that reads as a bug. */
.logo-field { margin-bottom: 14px; }
.logo-label {
  margin: 0 0 6px; font-size: 13px; line-height: 18px;
  font-weight: 600; color: var(--text-primary);
}
.logo-row { display: flex; align-items: center; gap: 12px; }
.logo-avatar {
  position: relative; flex-shrink: 0;
  display: inline-grid; place-items: center;
  width: 56px; height: 56px; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  background: var(--surface-subtle);
}
.logo-mono {
  font-size: 18px; font-weight: 700; letter-spacing: .02em;
  color: var(--text-secondary);
}
.logo-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: contain;
  background: var(--surface);
}
.logo-acts { display: flex; flex-wrap: wrap; gap: 6px; }

/* The context-quality meter. A real measurement of a stored row —
   Original's recomputed from browser state it never persisted, so
   it read ~25% on every second visit. */
.quality {
  background: var(--surface); padding: 12px 14px; margin: 0 0 12px;
  border: 1px solid var(--line); border-radius: var(--radius-panel);
}
.quality-head {
  display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px;
  font-size: 13px; color: var(--text-secondary);
}
.quality-n {
  font-size: 20px; line-height: 24px; font-weight: 700;
  color: var(--text-primary); font-variant-numeric: tabular-nums;
}
.quality-parts {
  list-style: none; margin: 10px 0 0; padding: 0;
  display: grid; gap: 3px 14px; font-size: 12px; line-height: 18px;
  color: var(--text-secondary);
}
@media (min-width: 641px) {
  .quality-parts { grid-template-columns: 1fr 1fr; }
}
.quality-parts .done { color: var(--success-ink); }

/* ===================== COLLAPSIBLE CATEGORIES =====================
   M-10 f3. `<details>`/`<summary>`, not a JS accordion: native gets
   keyboard operation, the correct disclosure announcement, and
   readability with the stylesheet removed (D-21) for free — and a
   scripted one has to earn all three back.

   `list-style: none` + the drawn marker because the default
   triangle sits outside the row and cannot be aligned with the
   text; `::marker` is unstyleable across engines. */
.cat-list { display: grid; gap: 8px; }
.cat {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-panel); overflow: hidden;
}
.cat > summary {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 14px; cursor: pointer; list-style: none;
  font-size: 14px; line-height: 20px;
  transition: background var(--t-fast) ease-out;
}
.cat > summary::-webkit-details-marker { display: none; }
.cat > summary::before {
  content: ""; flex: none; width: 8px; height: 8px;
  border-right: 2px solid var(--text-secondary);
  border-bottom: 2px solid var(--text-secondary);
  transform: rotate(-45deg);
  transition: transform var(--t-fast) ease-out;
}
.cat[open] > summary::before { transform: rotate(45deg); }
.cat > summary:hover { background: var(--surface-subtle); }
.cat-name { font-weight: 700; flex: 1; min-width: 0; }
.cat-count {
  flex: none; font-size: 12px; color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.cat-body { padding: 0 14px 14px; }
.cat-note {
  margin: 0 0 10px; font-size: 13px; line-height: 19px;
  color: var(--text-secondary);
}

/* ---- The review's read-back (CS-03) -------------------------
   One section per wizard step, each with its own Edit. The heading
   and the action share a row: the action belongs TO the section,
   and stacked above the fields it read as a page-level button. */
.ans {
  background: var(--surface); padding: 12px 14px; margin: 0 0 10px;
  border: 1px solid var(--line); border-radius: var(--radius-panel);
}
.ans-head {
  display: flex; align-items: center; gap: 12px; margin-bottom: 10px;
}
.ans-head h3 {
  margin: 0; flex: 1; min-width: 0;
  font-size: 14px; line-height: 20px; font-weight: 700;
}
.ans-head .btn { flex: none; }
.ans .kv-list { margin: 0; }

/* ---- The switch ---------------------------------------------
   A `role="switch"` button, so Space and Enter work and a screen
   reader announces two states. Drawn rather than a styled
   checkbox because the knob has to travel, and `appearance: none`
   on a checkbox loses the platform focus ring this keeps. */
.sw {
  display: inline-flex; align-items: center; gap: 0;
  padding: 4px; margin: 0; border: 0; background: none;
  cursor: pointer; border-radius: 999px;
}
.sw-track {
  display: block; position: relative;
  width: 36px; height: 20px; border-radius: 999px;
  background: var(--line-strong);
  transition: background var(--t-mid) ease-out;
}
.sw-knob {
  position: absolute; top: 2px; left: 2px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface); box-shadow: var(--shadow-panel, none);
  transition: transform var(--t-mid) ease-out;
}
.sw[aria-checked="true"] .sw-track { background: var(--success); }
.sw[aria-checked="true"] .sw-knob { transform: translateX(16px); }
.sw:hover .sw-track { outline: 2px solid var(--brand-line); }
/* The state must not be colour alone (§14). The row's Status pill
   carries the word, and the switch carries an sr-only label. */
.sw-static { font-size: 12px; color: var(--text-secondary); }

/* ===================== TABLES =====================
   §8: repeated comparable records only. The primary identifier is
   visually strongest, numbers align, row actions sit in a
   predictable place. Dense by default (D-22). */
.tbl-wrap { margin: 0; }
.tbl-actions {
  display: flex; gap: 8px; flex-wrap: wrap;
  justify-content: flex-end; margin-bottom: 8px;
}
table.tbl {
  width: 100%; border-collapse: collapse; font-size: 13px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  overflow: hidden;
}
table.tbl caption {
  text-align: left; padding: 0 0 8px;
  font-size: 12px; color: var(--text-secondary);
}
/* §8: "retain row actions in a predictable final column". Right
   aligned so the column reads as a column even when some rows
   have no action — and `width: 1%` lets the browser shrink it to
   its content instead of stealing space from the record. */
table.tbl .row-act { text-align: right; white-space: nowrap; }
table.tbl:not(.fixed) td.row-act { width: 1%; }

/* ---- Aligned columns across SEPARATE tables ------------------
   §8 wants the primary identifier strongest, numbers aligned and
   row actions in a predictable final column. On one table the
   browser's auto-sizing gets there by itself; across FOURTEEN
   category tables it does not — each measures its own content, so
   "Vendor" lands in fourteen different places and the eye has to
   re-find every column each time a category is opened.

   `table-layout: fixed` plus a shared `<colgroup>` fixes the edges
   once. The percentages total 100 and are ordered widest-content
   first, so `Description` gets the room and the two narrow columns
   do not stretch. */
table.tbl.fixed { table-layout: fixed; }
table.tbl.fixed .c-name { width: 22%; }
table.tbl.fixed .c-vendor { width: 12%; }
table.tbl.fixed .c-desc { width: 40%; }
table.tbl.fixed .c-date { width: 14%; }
table.tbl.fixed .c-act { width: 12%; }
/* A fixed layout will not shrink a header to fit, so a column
   narrower than its own uppercase, letter-spaced heading overflows
   INTO THE NEXT ONE — which rendered as "ACTIVATION DATBSYSTEM".
   Column headers therefore wrap, like the body cells already do. */
table.tbl.fixed th[scope="col"] {
  overflow-wrap: anywhere; white-space: normal;
}
/* And the ROW header must wrap too. `tbody th[scope="row"]` is
   `nowrap` by default — right on an auto-sized table where the
   column grows to fit, wrong on a fixed one, where "Microsoft
   Dynamics 365" simply ran into the Vendor column beside it. */
table.tbl.fixed tbody th[scope="row"] {
  white-space: normal; overflow-wrap: anywhere;
}
/* §8: numbers align. A date column is a number column for this
   purpose — right-aligned and tabular so the years form a line. */
table.tbl td.num, table.tbl th.num {
  text-align: right; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The connector brand mark (core/logo.js, ADR-M10-logos-restored).

   Two children in one 26px box, deliberately stacked rather than
   swapped: `.logo-mono` is the monogram and `.logo-img` is the
   fetched brand mark, and the image is HIDDEN until it has
   actually loaded. That ordering is what keeps all three states
   presentable — no token configured (no <img> is emitted at all),
   fetch failed (the <img> is removed), loaded (`has-logo` hides
   the monogram) — and is why nothing here ever shows a browser's
   broken-image glyph.

   `object-fit: contain` because brand marks are not square: a
   wordmark stretched to a 26px box is a defaced logo.

   **The image is hidden with `opacity`, NEVER with
   `display: none`** — and that is not a style preference, it is
   the difference between working and not. An image with no
   layout box is one the browser will not fetch, so it never
   loads, so `has-logo` is never added, so it stays hidden: a
   deadlock that presents as "the logos silently never appear".
   Verified in Chromium — the first cut of this rule used
   `display: none` and the page made **zero** requests with a
   token configured. `opacity: 0` keeps the box.

   **That fix was necessary and not sufficient, which is worth
   knowing before trusting this comment.** The page still made
   zero requests, because the same no-box condition also comes
   from the 13 CLOSED `<details>` the catalogue renders, and
   `loading="lazy"` never recovered when one opened. As of
   2026-08-12 the loading is no longer the browser's to schedule:
   the markup carries `data-src` and no `src`, and
   [core/logo.js](core/logo.js)'s `wireLogos()` starts each fetch
   from an `IntersectionObserver` plus a `toggle` listener on
   each `<details>`. A closed category still costs nothing —
   that saving is now enforced there rather than hoped for
   here. */
.conn { display: flex; align-items: center; gap: 8px; }
.conn-name { min-width: 0; overflow-wrap: anywhere; }
.logo {
  position: relative; flex: 0 0 auto; width: 26px; height: 26px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius); background: var(--surface-subtle);
  border: 1px solid var(--line); overflow: hidden;
}
.logo-mono {
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  color: var(--text-secondary);
}
.logo-img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: contain; opacity: 0;
}
.logo.has-logo { background: var(--surface); }
.logo.has-logo .logo-mono { visibility: hidden; }
.logo.has-logo .logo-img { opacity: 1; }

/* `06` b2: a status alone is incomplete — the basis sits beside
   it. Rendered as quiet secondary text on the same line, because
   promoting it would make the basis compete with the status it
   explains. */
.basis-line {
  display: block; margin-top: 2px; font-weight: 400;
  color: var(--text-secondary); font-size: 12px; line-height: 17px;
  overflow-wrap: anywhere;
}
/* An error is the one basis that must be findable while scanning
   65 rows, so it takes the danger ink. Never colour alone: it is
   prefixed with the word "Error". */
.basis-line.bad { color: var(--danger-ink); font-weight: 600; }

table.tbl th, table.tbl td {
  text-align: left; padding: 10px 12px; vertical-align: top;
  border-bottom: 1px solid var(--line);
}
table.tbl thead th {
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-secondary);
  background: var(--surface-subtle); white-space: nowrap;
}
table.tbl tbody tr:last-child th,
table.tbl tbody tr:last-child td { border-bottom: 0; }
table.tbl tbody th[scope="row"] { font-weight: 700; white-space: nowrap; }
table.tbl tbody tr:hover td, table.tbl tbody tr:hover th { background: var(--surface-subtle); }
.tbl-row-open { cursor: pointer; }
.tbl-row-open:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.tbl-empty { color: var(--text-secondary); text-align: center; }
/* Long user content must not blow out a cell, and numbers read
   down a column only with tabular figures. */
table.tbl td, table.tbl th[scope="row"] { overflow-wrap: anywhere; }
table.tbl td, table.tbl th[scope="row"],
.metric-v, .fact dd { font-variant-numeric: tabular-nums; }

/* `table-layout: fixed` only when a view declares column widths,
   so existing tables are untouched. Long values wrap instead of
   starving the columns to their right — the RCM's risk titles
   pushed two columns off-screen before this existed. */
table.tbl-fixed { table-layout: fixed; }
table.tbl-fixed td, table.tbl-fixed th { overflow-wrap: anywhere; }
/* And the ROW HEADER must wrap once the column can no longer
   widen. `th[scope="row"]` is `nowrap` above, which is right on
   an AUTO-sized table — an identifier stays on one line and the
   browser gives the column the room. A FIXED column cannot take
   the room, and `overflow-wrap` above is powerless against
   `nowrap`, so a long identifier simply ran into the column
   beside it: a control config's `removal_sla_bdays` was drawn
   on top of its own Type cell. The same fix already exists for
   `.doc-table`; it was never applied to the shared primitive. */
table.tbl-fixed tbody th[scope="row"] { white-space: normal; }
/* `tiny` is for an ORDINAL, not a value: the control config's
   decision table numbers its outcomes because first-match-wins
   makes the order the semantics, and a 116px column for "1"
   takes the room the condition beside it needs. */
col.col-tiny { width: 48px; }
col.col-code { width: 92px; }
col.col-narrow { width: 116px; }
col.col-mid { width: 200px; }

/* ===================== STATES =====================
   §8's full set, as one vocabulary: first-use empty, complete,
   filtered-empty, loading, partial, recoverable failure,
   permission-limited, blocked. Every one of them is a SENTENCE
   plus, where there is one, an action — never an icon and a
   shrug. */
.state {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 8px; padding: 24px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  color: var(--text-secondary);
}
.state.inset { border: 0; border-radius: 0; }
.state-ico { color: var(--text-secondary); }
.state h2, .state h3 {
  margin: 0; font-size: 14px; line-height: 20px; font-weight: 700;
  color: var(--text-primary);
}
.state p { margin: 0; font-size: 13px; max-width: 62ch; }
.state .btn { margin-top: 4px; }
.state.blocked { background: var(--warn-tint); border-color: var(--warn-line); }
.state.blocked .state-ico { color: var(--warn-ink); }
.state.failure { background: var(--danger-tint); border-color: var(--danger-line); }
.state.failure .state-ico { color: var(--danger-ink); }
.state.complete { background: var(--success-tint); border-color: var(--success-line); }
.state.complete .state-ico { color: var(--success-ink); }
.state.partial { background: var(--warn-tint); border-color: var(--warn-line); }
.state.partial .state-ico { color: var(--warn-ink); }
.state.permission .state-ico { color: var(--text-secondary); }

/* `.empty` is kept as an alias of the neutral state: it is used
   by a dozen call sites and by the drawer/table empties, and
   renaming it buys nothing. */
.empty {
  padding: 24px; text-align: center;
  color: var(--text-secondary); font-size: 13px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
}

/* ---- Loading skeleton --------------------------------------
   §8: it should resemble the final structure, and it must not
   animate when the user asks for reduced motion (honoured
   globally at the foot of this file). The pulse is opacity only
   — no sweeping highlight, which is decoration. */
.skeleton { display: flex; flex-direction: column; gap: 10px; }
.sk-row {
  height: 14px; border-radius: var(--radius);
  background: var(--line); animation: sk-pulse 1.4s ease-in-out infinite;
}
.sk-row.wide { width: 100%; }
.sk-row.mid { width: 62%; }
.sk-row.narrow { width: 34%; }
.sk-block {
  height: 56px; border-radius: var(--radius);
  background: var(--surface-subtle); border: 1px solid var(--line);
  animation: sk-pulse 1.4s ease-in-out infinite;
}
@keyframes sk-pulse { 50% { opacity: .55; } }

/* ===================== DRAWERS AND DIALOGS =====================
   §8: a dialog for a short focused decision that blocks other
   work; a drawer for supporting evidence, history or
   configuration that must preserve the selected record. These
   are the two things allowed a shadow (§7). */
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 460px; max-width: 94vw; z-index: 900;
  overflow-y: auto;
  background: var(--surface); border-left: 1px solid var(--line);
  box-shadow: var(--shadow-overlay);
  padding: 16px 20px 28px;
}
/* The drawer takes focus programmatically on open so the next
   Tab lands inside it. It is a container, not a control, so it
   shows no ring — the ring belongs to the close button, which is
   the first thing Tab reaches. */
.drawer:focus { outline: none; }
.drawer-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 12px;
  position: sticky; top: -16px; padding: 12px 0 10px;
  background: var(--surface); border-bottom: 1px solid var(--line);
}
.drawer-head h2 { margin: 0; font-size: 16px; line-height: 24px; }
.detail p { margin: 0 0 12px; line-height: 1.5; }
.detail h3 {
  margin: 16px 0 6px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-secondary);
}
.detail-list { margin: 0 0 12px; padding-left: 18px; line-height: 1.6; }
/* The buttons under a drawer's form. A row rather than a stack
   because they are alternatives to one another — confirm, reject,
   defer — and stacking them reads as a sequence. */
.detail-actions { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }
.muted { color: var(--text-secondary); }

/* The modal (log / package / config viewer). `.cc-modal*` names
   are kept: dom.js, the workflow channel and the Command Center
   all reach for them. */
.cc-modal {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: rgba(15, 23, 42, .45);
}
.cc-modal[hidden] { display: none; }
.cc-modal-card {
  display: flex; flex-direction: column;
  width: min(860px, 100%); max-height: 88vh; overflow: hidden;
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: var(--radius-panel); box-shadow: var(--shadow-overlay);
}
/* The FILE VIEWER's card. 860px is right for a log and too
   narrow for a document, so the viewer opts into a wider one
   rather than widening the default under the log and package
   viewers that were measured against it. */
.cc-modal-card.wide { width: min(1200px, 100%); max-height: 92vh; }

.cc-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 16px;
  background: var(--surface-subtle); border-bottom: 1px solid var(--line);
}
.cc-modal-title { font-size: 14px; font-weight: 700; }
.cc-modal-body { padding: 16px; overflow-y: auto; }

/* ---- the file viewer ----------------------------------------
   Rendered from a SHAPE the server sent (rows, text) — never
   from the uploaded file, which is why nothing here is a
   sandboxed frame or an <object>. */
.fv { min-height: 120px; }
.fv-state {
  padding: 24px 8px; text-align: center;
  color: var(--text-secondary); font-size: 13px;
}
.fv-failed { color: var(--danger-ink); }
.fv-note {
  margin: 8px 0 0; font-size: 12px; color: var(--text-muted);
}
/* `pre`, so an evidence file's own indentation survives, and
   wrapped, so a 400-character line does not scroll the card. */
.fv-text {
  margin: 0; font-family: var(--font-mono); font-size: 12px;
  line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere;
  color: var(--text-primary);
}
/* Paged documents. One <img> per page, stacked, each the
   server's own render — there is no PDF in the browser, which
   is the point. */
.fv-pages { display: flex; flex-direction: column; gap: 12px; }
.fv-page {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}

/* A 60-column export scrolls INSIDE the card, never the page. */
.fv-table-wrap { overflow-x: auto; }
.fv-table {
  border-collapse: collapse; font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.fv-table th,
.fv-table td {
  padding: 4px 10px; text-align: left; white-space: nowrap;
  border: 1px solid var(--line);
}
.fv-table th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface-subtle); font-weight: 600;
}
.fv-table tbody tr:nth-child(even) { background: var(--surface-subtle); }

/* ===================== COPILOT =====================
   §9: contextual assistance, not the page architecture. Closed
   to a 44px rail by DEFAULT, opening to ~384px, resizable
   between 320 and 560 while capped at ~45% of the viewport —
   whichever is tighter. Nothing here is styled as "AI": no
   sparkle, no glow, no gradient (§4). */
.assistant {
  position: relative; min-width: 0;
  display: flex; flex-direction: column;
  background: var(--surface); border-left: 1px solid var(--line);
  overflow: hidden;
}
/* Closed: the whole rail is the opener, labelled vertically, so
   the entry point is bound to the record on screen rather than
   parked in a menu. */
.asst-rail {
  display: flex; width: 100%; height: 100%;
  align-items: center; justify-content: center;
  border: 0; background: none; color: var(--brand-dark);
  cursor: pointer; padding: 8px 0;
}
.asst-rail:hover { background: var(--brand-tint); }
.asst-rail:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; }
.asst-rail-label {
  writing-mode: vertical-rl; transform: rotate(180deg);
  font-size: 12px; font-weight: 700; letter-spacing: .02em;
  white-space: nowrap;
}
.asst-panel { display: none; }
.layout.copilot-open .asst-rail { display: none; }
.layout.copilot-open .asst-panel {
  display: flex; flex-direction: column;
  height: 100%; min-width: 0; min-height: 0;
}
/* A visible separator with pointer AND keyboard operation (§9).
   `role="separator"` with aria-valuenow/min/max is set in the
   markup; this is only its appearance. */
.asst-resizer {
  position: absolute; z-index: 3; inset: 0 auto 0 -3px;
  width: 9px; cursor: col-resize; touch-action: none;
  border: 0; background: none; padding: 0;
  display: none;
}
.layout.copilot-open .asst-resizer { display: block; }
.asst-resizer::after {
  content: ""; position: absolute; inset: 0 auto 0 3px;
  width: 3px; background: transparent;
  transition: background var(--t-fast) ease-out;
}
.asst-resizer:hover::after, .asst-resizer:focus-visible::after,
.asst-resizer.dragging::after { background: var(--brand); }
.asst-resizer:focus-visible { outline: none; }

.asst-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; min-height: var(--topbar-h); padding: 8px 12px;
  border-bottom: 1px solid var(--line);
}
/* `min-width: 0` on BOTH the flex row's growing child and the
   text block inside it. Without the inner one the title and
   subtitle refuse to shrink below their content width and slide
   underneath the model picker — which is what a 384px panel does
   to any head that assumes it has room. */
.asst-brand {
  display: flex; align-items: center; gap: 9px;
  min-width: 0; flex: 1 1 auto;
}
.asst-brand-text { min-width: 0; }
.asst-brand .ico { color: var(--text-secondary); }
/* Two lines, and the break falls at the SPACE. Product-owner
   decision, 2026-07-28.
   Three separate requirements, and each needs its own property:
     * it must not truncate — "Compliance Co…" is not a shorter
       version of the panel's name, it is a different word;
     * it must not break INSIDE a word, which is what the first
       version's `overflow-wrap: anywhere` allowed as soon as the
       panel narrowed ("Complianc / e Copilot");
     * it must be two lines at every panel width between 320 and
       560px, not one line when there happens to be room.
   `max-width: 12ch` is what makes the third deterministic
   without a forced `<br>`: "Compliance" is 10ch so it stays on
   line one, "Copilot" plus the dot is about 9.5ch so it fits on
   line two, and the 18ch single line cannot. */
.asst-title {
  font-size: 13px; line-height: 1.3; font-weight: 700;
  max-width: 12ch; overflow-wrap: normal;
  /* A FLOOR as well as a ceiling. At a 320px panel the model
     picker and two buttons left the title 64px, and since it may
     not break inside a word it went to three lines instead of
     two. 10ch is what "Compliance" needs; the picker shrinks
     instead, which is the right thing to give up — it is a
     control whose current value is also written in the reply. */
  min-width: 10ch;
}
.asst-ctl {
  display: flex; align-items: center; gap: 6px;
  flex: 0 1 auto; min-width: 0;
}
/* ONE picker component, worn by two controls: the Copilot's model
   and the topbar's upload destination. Both answer "which of the
   engine's things am I using?", both sit in a 56px bar beside an
   icon button, and two rule blocks with the same values would
   drift the moment one of them was tuned. `.topbar-select` is the
   neutral name; `.asst-model` is kept because the Copilot head's
   layout selectors already key off it. */
.asst-model, .topbar-select {
  /* Wide enough that the native dropdown arrow does not sit on
     top of the last character — "Claude Opus 4.8" plus an arrow
     needs the room, and the head has it now that the framework
     sub-line is gone. */
  /* `padding-right` keeps the native dropdown arrow off the last
     glyph — a select does not ellipsize, it clips, and an arrow
     sitting on top of a half-drawn character reads as a
     rendering fault. The floor is what the arrow plus a few
     characters need; below that the button is not a picker, it is
     a puzzle. */
  height: 30px; max-width: 148px; min-width: 92px;
  padding: 0 20px 0 7px;
  font-size: 12px; color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  cursor: pointer;
}
.asst-model:hover, .topbar-select:hover {
  border-color: var(--brand);
}
/* The picker starts `hidden` and stays hidden for a session that
   may read the drop and not feed it — no rule is needed for that
   here, because the `[hidden] { display: none !important }` at the
   top of this file already outranks anything a component sets. */
/* Connection state: a coloured dot, with the word on hover
   (`title`) and in the accessibility tree (an `.sr-only` span
   inside the same `role="status"`). Product-owner decision,
   2026-07-28.
   §14's rule is against meaning carried by colour ALONE, which
   this is not — the word is present twice and only the dot is
   visual. The dot takes a darker border as well as a fill, so it
   stays a distinct mark for someone who cannot separate the
   hues. */
/* Inline, so it follows the title's last letter rather than
   occupying a slot of its own. `vertical-align` centres it on the
   text's x-height instead of its baseline, where a circle sits
   visibly low. */
.asst-conn {
  display: inline-flex; align-items: center;
  margin-left: 6px; vertical-align: middle; cursor: default;
}
.asst-dot {
  width: 9px; height: 9px; border-radius: 50%; flex: none;
  background: var(--text-muted);
  border: 1px solid var(--line-strong);
}
.asst-conn.ok .asst-dot {
  background: var(--success); border-color: var(--success-ink);
}
.asst-conn.err .asst-dot {
  background: var(--danger); border-color: var(--danger-ink);
}

.asst-log {
  flex: 1; min-height: 0; overflow-y: auto;
  padding: 14px; display: flex; flex-direction: column; gap: 10px;
}
.msg {
  max-width: 94%; padding: 9px 11px; border-radius: var(--radius-panel);
  font-size: 13px; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word;
}
.msg.you {
  align-self: flex-end; background: var(--brand); color: var(--surface);
  border-bottom-right-radius: 3px;
}
.msg.bot {
  align-self: flex-start; background: var(--msg-ai);
  color: var(--text-primary); border: 1px solid var(--line);
  border-bottom-left-radius: 3px; white-space: normal;
}
.msg.tool {
  align-self: flex-start; background: var(--warn-tint);
  color: var(--warn-ink); border: 1px solid var(--warn-line);
  font-size: 12px;
}
.msg.err {
  align-self: flex-start; background: var(--danger-tint);
  color: var(--danger-ink); border: 1px solid var(--danger-line);
}
/* A REPLY THE READER STOPPED.
 *
 * `.msg.interrupted` faded and italicised the WHOLE bubble
 * until 2026-09-23 — which dimmed the answer as a way of
 * labelling it, and the answer is the part worth reading: a
 * reader stops a reply precisely when they have seen enough.
 * So the text is left alone and the note carries the meaning,
 * set off by a rule the way a footnote is. */
.msg.stopped .msg-body { opacity: 1; font-style: normal; }
.msg-stopped {
  display: flex; align-items: center; gap: 6px;
  margin-top: 8px; padding-top: 6px;
  border-top: 1px solid var(--line);
  font-size: 11px; color: var(--text-muted);
}
.msg-stopped svg {
  width: 12px; height: 12px; flex: 0 0 auto;
  /* The stop icon is the one on the button they just pressed,
   * so the note points back at their own gesture. */
  color: var(--text-muted);
}
/* §9's response contract, rendered: every assistant turn says
   what kind of claim it is. */
.msg-prov { display: flex; gap: 6px; margin-bottom: 6px; }
.asst-suggest {
  display: flex; gap: 6px; flex-wrap: wrap; padding: 0 12px 8px;
}
.asst-suggest .sg {
  min-height: 30px; padding: 4px 10px;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  background: var(--surface); color: var(--text-secondary);
  font-size: 12px; cursor: pointer; text-align: left;
}
.asst-suggest .sg:hover { border-color: var(--brand); color: var(--brand-dark); }
.asst-input {
  display: flex; gap: 8px; align-items: flex-end;
  padding: 10px 12px; border-top: 1px solid var(--line);
}
.asst-input textarea {
  flex: 1; min-width: 0; resize: none; max-height: 132px;
  padding: 8px 10px; font-size: 13px; color: var(--text-primary);
  background: var(--surface-subtle);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.asst-input textarea::placeholder { color: var(--text-muted); }
.asst-input textarea:focus-visible { background: var(--surface); }
.asst-input .btn { min-height: 36px; min-width: 36px; padding: 0 10px; }
.asst-input .btn.sending {
  background: var(--surface); color: var(--danger-ink);
  border-color: var(--danger-line);
}

/* ===================== WORKFLOW RUN (vagent channel) ========= */
.wf-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 12px;
}
.wf-status {
  font-size: 13px; font-weight: 700; color: var(--text-secondary);
}
.wf-status.ok { color: var(--success-ink); }
.wf-status.err { color: var(--danger-ink); }
/* The third tone, added with the frozen-run state (ADR-113).
   `warn` is this repo's existing tone word — `.pill.warn`,
   `.meter.warn`, `tone: "warn"` across the views — so the run
   channel reaches for it rather than inventing a fourth. NOT
   `err`: a run paused for a decision is alive and waiting, and
   red would say it broke. Without this rule the status line
   rendered in default ink, which read as an ordinary progress
   message rather than as something waiting on a person. */
.wf-status.warn { color: var(--warn-ink); }
.wf-summary { font-size: 13px; color: var(--text-secondary); margin: 4px 0 0; }
.wf-detail-btn { margin-left: auto; }

.wf-nodes { display: flex; flex-direction: column; gap: 0;
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  overflow: hidden; }
.wf-node {
  display: grid; grid-template-columns: 210px minmax(0, 1fr);
  gap: 14px; align-items: start;
  padding: 11px 13px; background: var(--surface);
  border-bottom: 1px solid var(--line);
  border-left: 3px solid transparent;
}
.wf-node:last-child { border-bottom: 0; }
.wf-node-head {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 700;
}
.wf-check {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; flex: none; border-radius: 50%;
  border: 1.5px solid var(--line-strong);
  color: var(--text-secondary); background: var(--surface);
}
.wf-node-name { overflow-wrap: anywhere; }
.wf-node-out {
  color: var(--text-secondary); font-size: 13px; min-width: 0;
  white-space: normal;
}
.wf-node-out > :first-child { margin-top: 0; }
.wf-node-out > :last-child { margin-bottom: 0; }
/* Step state: a border colour, an icon AND the word in
   `.wf-state` — three signals, because one is a colour. */
.wf-node.pending { color: var(--text-secondary); }
.wf-node.running { border-left-color: var(--brand); }
.wf-node.running .wf-check { border-color: var(--brand); color: var(--brand); }
.wf-node.done { border-left-color: var(--success); }
.wf-node.done .wf-check {
  background: var(--success-tint); border-color: var(--success);
  color: var(--success-ink);
}
.wf-node.error { border-left-color: var(--danger); }
.wf-node.error .wf-check {
  background: var(--danger-tint); border-color: var(--danger);
  color: var(--danger-ink);
}
.wf-node.error .wf-node-out { color: var(--danger-ink); }
.wf-state {
  margin-left: auto; font-size: 12px; font-weight: 600;
  color: var(--text-secondary);
}
.wf-modal-wrap { display: flex; flex-direction: column; gap: 12px; }
.wf-modal-wrap .wf-summary { margin: 0; font-weight: 700; }

/* ===================== COMMAND CENTER ===================== */
/* The record's name on the left, its asides right-justified and
   level with it. `align-items: start` so the aside sits with the
   TITLE rather than centring against a two-line name block, and
   `flex-wrap` so a narrow pane drops it under the name instead
   of crushing either. The DOM order is name → sub → aside; only
   this rule lifts the aside beside the title, which is what
   keeps D-21's stylesheet-off read in the record's own order. */
.cc-detail-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 12px;
}
.cc-head-main { min-width: 0; }
/* Centred against the whole name block, so it lands on the
   NAME's line rather than on the status pill above it.
   DELIBERATELY UNCAPPED, and it holds one control.
   A design review at 1280px is why: it used to hold the design's
   status, its version AND the button as a fixed `max-width: 20rem`
   block, and this pane is variable-width — so anything under a
   ~590px pane wrapped the whole thing onto its own line, where it
   floated mid-pane aligned to nothing. Keep this host to a single
   short actionable; record STATE belongs in `facts()` below,
   which already owns alignment and wrapping. */
.cc-head-aside {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 8px;
}
.cc-head-aside:empty { display: none; }
/* 44px of TOUCH TARGET without 44px of button. DESIGN.md §8 asks
   for the target; the button is `.btn.sm` (30px) and matching
   `Show detail` and `Run` beside it matters more than matching
   the number, so the hit area is extended past the box instead
   of the box being grown. Safe here specifically because this
   button has no neighbour within the 7px it reaches — see the
   note in the audit about `.btn.sm` being 30px app-wide, which
   is a system-level decision and not this button's to make. */
.cc-cfg-btn { position: relative; }
.cc-cfg-btn::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: 50%; height: 44px; transform: translateY(-50%);
}
.cc-detail-head h2 {
  margin: 4px 0 3px; font-size: 17px; line-height: 24px; font-weight: 700;
}
.cc-detail-head .sub { color: var(--text-secondary); font-size: 12px; }

/* `.cc-design-inline`, `.cc-design-facts` and `.cc-design-lbl`
   were deleted here. They positioned the design status and
   version in the record head; both are now a `facts()` row, and
   `facts()` owns its own label weight, alignment and wrapping.
   A cluster with its own layout rules beside a grid that already
   had them was the duplication the 1280px review found. */
.cc-actions {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 8px;
}
.cc-note { font-size: 13px; color: var(--success-ink); }
.cc-note.on { font-weight: 700; }
.cc-note:empty { display: none; }

/* Run history (read-only past runs) */
.cc-run {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 11px; align-items: center;
  padding: 9px 12px; background: var(--surface-subtle);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.cc-run-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--line-strong);
  color: var(--text-secondary); background: var(--surface);
}
.cc-run-badge.done { color: var(--success-ink); border-color: var(--success);
  background: var(--success-tint); }
.cc-run-badge.error, .cc-run-badge.killed, .cc-run-badge.cancelled {
  color: var(--danger-ink); border-color: var(--danger);
  background: var(--danger-tint); }
.cc-run-badge.paused { color: var(--warn-ink); border-color: var(--warn);
  background: var(--warn-tint); }
.cc-run-badge.running { color: var(--brand-dark); border-color: var(--brand);
  background: var(--brand-tint); }
.cc-run-main { min-width: 0; }
.cc-run-when { font-size: 13px; font-weight: 700; }
.cc-run-meta { color: var(--text-secondary); font-size: 12px; }
.cc-run-acts { display: flex; gap: 6px; flex: none; }
.cc-run-detail:empty { display: none; }
.cc-run-detail { margin-top: 10px; }

/* Run history lives INSIDE the processing panel (2026-08-07),
   under a rule rather than in a panel of its own: this run and
   the runs before it are one subject, and two stacked boxes said
   so twice while costing the record pane a whole panel's chrome.
   An `h4` rather than an `h3` because it is subordinate to
   "Control processing" — the heading level carries that, so a
   text-only read (D-21) gets the nesting the border draws. */
/* The run's decision, directly under the run bar it decides.
   Tighter than a free-standing `.cc-actions` because it is part
   of the panel above it, not a new subject. */
.wf-decide { margin-top: 12px; }
.wf-decide .cc-design-hint { margin-top: 6px; width: 100%; }
.wf-decide .cc-design-hint[hidden] { display: none; }

.cc-hist {
  margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--line);
}
.cc-hist h4 {
  margin: 0 0 8px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  color: var(--text-secondary);
}

/* Prepared package (the run's stage artifacts) */
.cc-package:empty { display: none; }
.cc-package {
  margin-top: 12px; padding-top: 12px;
  border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 8px;
}
.cc-pkg-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; font-size: 13px; color: var(--text-secondary);
}
.cc-pkg-head b { color: var(--text-primary); }
.cc-pkg-item {
  border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; background: var(--surface);
}
.cc-pkg-name {
  display: flex; align-items: center; gap: 9px; width: 100%;
  min-height: 36px; padding: 8px 12px;
  border: 0; background: var(--surface-subtle);
  color: var(--text-primary); font-size: 13px; font-weight: 700;
  text-align: left; cursor: pointer;
}
.cc-pkg-name:hover { background: var(--brand-tint); }
.cc-pkg-name .cc-pkg-lbl { flex: 1; min-width: 0; }
.cc-pkg-chev { color: var(--text-secondary); transition: transform var(--t-mid); }
.cc-pkg-item.open .cc-pkg-chev { transform: rotate(180deg); }
.cc-pkg-body {
  padding: 12px 14px; font-size: 13px; color: var(--text-secondary);
  border-top: 1px solid var(--line);
}
.cc-pkg-body > :first-child { margin-top: 0; }
.cc-pkg-body > :last-child { margin-bottom: 0; }

/* Control-design review lifecycle (the config screen's Design
   tab). `.cc-design-head` and `.cc-cfg-btn { margin-left: auto }`
   were deleted with the panel they positioned: the summary moved
   into the record head, where `.cc-head-aside` does the
   right-justifying, and an `auto` margin left behind would push
   the Configure button away from the version it belongs with. */
.cc-design-ver { font-size: 12px; color: var(--text-secondary); }
.cc-design-meta {
  font-size: 13px; color: var(--text-secondary); margin: 0 0 10px;
}
/* --text-secondary, not --text-muted: this is CONTENT (it states
   that sign-off is human), and muted is reserved for placeholder
   and disabled, where it sits at 2.56 on white. The live-DOM
   contrast sweep caught it. */
.cc-design-hint {
  font-size: 12px; color: var(--text-secondary);
  margin-top: 10px; line-height: 1.5;
}
.cc-rev { max-width: 220px; }
.cc-cfg { display: flex; flex-direction: column; gap: 12px; }
.cc-cfg .panel-box { margin: 0; }
.cc-cfg-p { margin: 0; color: var(--text-secondary); font-size: 13px;
  line-height: 1.55; }
.cc-cfg-list { margin: 0; padding-left: 20px; color: var(--text-secondary);
  font-size: 13px; }
.cc-cfg-list li { margin: 3px 0; }
.cc-cfg .toolbar { margin: 0; }

/* ---- The Control config screen (command_center/config.js) ----
   Three tabs over one modal body: the design record and its
   review, the configuration as authored, and the editor. The tab
   bar reuses `.facets` — the same "same subject, different lens"
   control the Command Center's own lanes use — so a reader meets
   one selection idiom, not two. */
.cc-cfg-screen { display: flex; flex-direction: column; gap: 12px; }
.cc-cfg-tabs { margin: 0; }
.cc-cfg-body { display: flex; flex-direction: column; gap: 12px; }
.cc-cfg-body > .panel-box, .cc-cfg-body > .cat { margin: 0; }
.cc-cfg-body .tbl-wrap { margin: 8px 0 0; }
/* The source-and-provenance panel leads the reading tab, and it
   is deliberately tinted: ADR-049 makes vagent's copy
   authoritative, so "this is the authored text" is a caveat on
   everything below it rather than one more fact among them. */
.cc-cfg-prov { background: var(--surface-subtle); }
.cc-cfg-lbl {
  margin: 10px 0 4px; font-size: 12px; font-weight: 700;
  color: var(--text-secondary);
}
.cc-cfg-tech { margin-top: 4px; }
.cc-cfg-tech .cat-body > .panel-box:first-child { margin-top: 0; }

/* The editor. Monospace because it is a file, and `resize:
   vertical` because a config runs to 300 lines and a fixed box
   turns reading it into scrolling a porthole. */
.cc-cfg-edit {
  width: 100%; box-sizing: border-box; resize: vertical;
  min-height: 220px; padding: 10px 12px;
  font-family: var(--font-mono); font-size: 12px; line-height: 1.6;
  color: var(--text-primary); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.cc-cfg-raw {
  margin: 0; max-height: 320px; overflow: auto; padding: 10px 12px;
  font-family: var(--font-mono); font-size: 12px; line-height: 1.6;
  color: var(--text-secondary); background: var(--surface-subtle);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.cc-cfg-check-out { display: flex; flex-direction: column; gap: 8px; }
/* A verdict is TEXT with a tone, never a colour alone (§8's pill
   contract, applied to a status line). Both words survive the
   stylesheet being removed. */
.cc-cfg-ok {
  margin: 0; font-size: 13px; color: var(--success-ink);
}
.cc-cfg-bad {
  margin: 0; font-size: 13px; font-weight: 700;
  color: var(--danger-ink);
}

/* ===================== RUN VIEWER (M-03 f9) ===================== */
.run-head h3 { margin: 0 0 10px; font-size: 14px; line-height: 20px; }
.run-view h4 {
  margin: 16px 0 8px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-secondary);
}
.run-ok { color: var(--success-ink); font-weight: 700; }
.run-bad { color: var(--danger-ink); font-weight: 700; }
.run-timeline { list-style: none; margin: 0; padding: 0 0 0 6px; }
.run-ev {
  position: relative; display: flex; gap: 12px;
  padding: 0 0 14px 14px; border-left: 2px solid var(--line);
}
.run-ev:last-child { border-left-color: transparent; }
.run-dot {
  position: absolute; left: -6px; top: 4px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--brand); box-shadow: 0 0 0 3px var(--surface);
}
.run-ev-bad .run-dot { background: var(--danger); }
.run-ev-body { flex: 1; min-width: 0; }
.run-ev-head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.run-ev-title { font-size: 13px; font-weight: 700; }
.run-when {
  margin-left: auto; font-size: 12px; color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.run-issue {
  font-size: 12px; color: var(--brand-dark);
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 1px 8px;
}
.run-issue:hover { background: var(--brand-tint); text-decoration: none; }
/* The step-detail CONTAINER. Its block elements are styled by
   `.md` below — the class setMarkdown() adds — so only the
   container's own type scale lives here. */
.run-detail { font-size: 13px; line-height: 1.5; margin-top: 4px; }

/* ---- Screen 01: the actor, what it advanced, and the trail ----
   M-03 f23. Every colour here is an EXISTING token — the design
   bundle's prototype uses 24 raw hexes and a Google Fonts link,
   of which only #0061FE exists in this product, so nothing is
   lifted from it directly. No new token is introduced, because
   DESIGN.md and this file are pinned to each other token by
   token and a new one moves both in the same turn. */
.run-ev-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 4px;
}
.run-actor {
  font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-secondary);
}
.run-actor-human { color: var(--brand-dark); }
.run-step {
  font-weight: 400; text-transform: none; letter-spacing: 0;
  color: var(--text-secondary);
}
.run-attrs { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.run-attr {
  font-size: 11px; border: 1px solid var(--line-strong);
  border-radius: 999px; padding: 0 7px;
  font-variant-numeric: tabular-nums;
}
/* NOT a blank. Until a ctrl-* skill emits `attribute_ids` this
   is every row of every real run, and the bundle's rule is that
   a gap is displayed, never suppressed. */
.run-attrs-none, .run-trail-none {
  font-size: 11px; font-style: italic;
  color: var(--text-secondary);
}
.run-det { font-size: 11px; color: var(--text-secondary); }
.run-det-inferential { color: var(--warn-ink); }
.run-trail { margin-top: 4px; }
.run-trail > summary {
  font-size: 12px; color: var(--brand-dark); cursor: pointer;
  width: max-content;
}
.run-hash {
  font-size: 11px; color: var(--text-secondary); margin: 4px 0 0;
}
/* ---- Screen 01: the trail as DATA (E-215) ----
   A definition list, not prose. Existing tokens only, for the
   reason the block above gives. `.run-tl-bad` is the same
   --danger-ink the packet's blockers use, so a refused claim
   and a failed precondition read as the same class of thing. */
.run-arts, .run-unattested {
  font-size: 11px; color: var(--text-secondary);
}
.run-arts-none { font-style: italic; }
.run-unattested {
  color: var(--warn-ink); font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
}
.run-trail-data { margin: 6px 0 0; font-size: 11.5px; }
.run-tl {
  display: grid; grid-template-columns: 168px minmax(0, 1fr);
  gap: 0 10px; padding: 2px 0;
  border-top: 1px solid var(--line);
}
.run-tl:first-child { border-top: 0; }
.run-tl > dt {
  color: var(--text-secondary); font-weight: 600;
  overflow-wrap: anywhere;
}
.run-tl > dd { margin: 0; overflow-wrap: anywhere; }
.run-tl-bad > dd { color: var(--danger-ink); }
@media (max-width: 720px) {
  .run-tl { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Screen 01: the coverage header and stage rail ---- */
.run-cov {
  border: 1px solid var(--line); border-radius: 8px;
  padding: 10px 12px; margin: 0 0 12px;
}
.run-cov-head { margin: 0; font-size: 13px; font-weight: 700; }
.run-cov-none { font-size: 13px; margin: 0 0 12px; }
.run-rail {
  list-style: none; display: flex; flex-wrap: wrap; gap: 6px 14px;
  margin: 8px 0 0; padding: 0;
}
.run-rail-stage { font-size: 12px; }
.run-rail-name { font-weight: 700; }
.run-rail-n {
  color: var(--text-secondary); margin-left: 6px;
  font-variant-numeric: tabular-nums;
}
.run-rail-stage.has-gap .run-rail-name { color: var(--warn-ink); }
.run-cov-undeclared {
  font-size: 12px; margin: 8px 0 0; color: var(--warn-ink);
}

/* ---- Layout B: the same component in a narrow column ----
   The bundle's 01B. It is DENSITY only — the markup is
   identical, because two render paths would be two viewers and
   `wfViewLog` already refuses that. What changes: the coverage
   rail stacks instead of flowing, the trail stays collapsed,
   and the metadata line wraps under the title rather than
   competing with it for horizontal room the modal does not
   have. No colour differs; a compact reading of an audit
   record must not be a different reading of it. */
.run-view-compact .run-rail {
  flex-direction: column; gap: 2px;
}
.run-view-compact .run-cov { padding: 8px 10px; }
.run-view-compact .run-ev-head { gap: 6px; }
.run-view-compact .run-ev-meta { margin-top: 2px; }
.run-view-compact .run-when {
  margin-left: 0; flex-basis: 100%; order: 9;
}
.run-view-compact .run-detail { font-size: 12px; }
.run-evidence { list-style: none; margin: 0; padding: 0; }
.run-evidence li {
  display: flex; gap: 8px; align-items: baseline; padding: 4px 0;
  font-size: 13px;
}
.run-evidence code { font-size: 12px; color: var(--text-secondary); }
.ev-body {
  background: var(--surface-subtle); border: 1px solid var(--line);
  padding: 10px; border-radius: var(--radius);
  font-size: 12px; overflow-x: auto; white-space: pre-wrap;
}

/* ---- D-39 segregation-of-duties banner ----------------------
   Shared by the run viewer and the deficiency drawer (C-35: one
   detector, one banner). Amber, NOT red: the record is valid and
   complete, and this is a finding to evaluate rather than an
   error to clear (ADR-015). Red would say the write failed.

   No dismiss control on purpose — a dismissible banner is one
   that gets dismissed, and this one is permanent. */
.sod-banner {
  background: var(--warn-tint);
  border: 1px solid var(--warn-line); border-left: 4px solid var(--warn);
  border-radius: var(--radius-panel);
  padding: 11px 13px; margin: 12px 0; font-size: 13px;
}
.sod-banner b { color: var(--warn-ink); }
.sod-banner ul { margin: 5px 0 6px; padding-left: 20px; }
.sod-banner li { margin: 2px 0; }
.sod-banner p { margin: 0; font-size: 12px; }

/* ---- The frozen-run approval panel (ADR-113, plan §5.3) -----
   Amber like the SOD banner above and for the same reason: this
   is a state to act on, not a failure. Red would say the run
   broke; it did not — it is alive and waiting.

   `role="status"`, not `alert`: it is a property of the record
   being read. The run froze whenever it froze; a screen reader
   arriving at the page is not being told something just
   happened.

   No token is invented here — every value is a custom property
   DESIGN.md §5/§6 already pins. */
/* NOT AMBER, SINCE 2026-09-10, AND AT THE SOURCE.
   It was `--warn-tint` with a 4px `--warn` rail and a
   `--warn-ink` heading. Removed at the product owner's
   instruction — *"remove yellow completely"* — after three
   passes that each took it out of one surface and left it in
   the next: the rail's card, then the frozen dialog, then the
   rail's own dialog. Overriding per surface was the wrong shape
   for a decision that applies to the component.

   What replaces it is the panel look every other card in cb-app
   uses. Urgency is carried by the surface an approval appears
   ON — a dialog that exists only while something is waiting —
   and by the answer buttons being present at all. */
/* The node that is ASKING. A `call_human` question lands in the
   row of the node the run is paused at — `attr-judge` is a
   `wf/skill`, so unlike an approval node there is no type that
   means "waiting", and the row would otherwise sit `running`
   while a person is being asked something they cannot see. */
.exec-node-ask:empty { display: none; }
.exec-node-ask { margin: 6px 0 0; }
/* Denser here than on a full-width surface: this is a 384px
   column and the question prose is long. Same element, same
   builder — the surroundings differ, never the panel. */
.exec-node-ask .run-question { margin: 0; font-size: 12px; }
.exec-node-ask .run-q-ask { font-size: 12px; }

/* ---- the question, clamped in the 384px shelf ---------------
   Product-owner direction 2026-09-15: in the side chat the card
   is short with a link to expand; with the chat CLOSED the
   frozen stack shows it centred instead. Two states, one
   builder — what differs is the surroundings.

   Scoped to the shelf host ON PURPOSE, exactly as the rail's
   clamp is: `ccModalShow` MOVES this element into the dialog
   rather than copying it, so a clamp that lived on the panel
   would follow it there and clamp the very thing the link was
   pressed to read. */
.asst-q-clamped .run-q-ask {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3; overflow: hidden;
}
.asst-q-clamped .run-q-opts,
.asst-q-clamped .run-q-commit,
.asst-q-clamped .run-question-note { display: none; }
/* Everything past the first question is out of sight while
   clamped — a five-question set would otherwise fill the panel
   somebody came here to type in. */
.asst-q-clamped .run-q ~ .run-q { display: none; }
.asst-q-more {
  display: block; margin: 4px 0 8px; padding: 0;
  font: inherit; font-size: 11px; font-weight: 700;
  color: var(--brand-dark); background: none; border: 0;
  cursor: pointer; text-align: left;
}
.asst-q-more:hover { text-decoration: underline; }
/* Inside the dialog there is nothing left to expand. */
.cc-modal .asst-q-more { display: none; }

/* ---- the OTHER record: a call_human question set ------------
   1-5 single-select questions, each with its own options and a
   free-text `Other`. Shares the approval panel's frame on
   purpose — a person meets both on the same surfaces and a
   second visual language would read as a second kind of act,
   which it is not (ADR-113 covers both). What differs is the
   INSIDE: radio groups and one submit, where an approval is a
   row of buttons that each commit. */
.run-question {
  background: var(--surface-subtle);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--radius-panel);
  padding: 11px 13px; margin: 12px 0; font-size: 13px;
}
.run-question b { color: var(--text-primary); }
/* The engine's own title for the set — attribute, control and
   run. `translate="no"` in the markup; this only sets it apart
   from the prose below. */
.run-q-where {
  margin: 4px 0 0; color: var(--text-secondary);
  font-size: 12px;
}
.run-q { margin: 11px 0 0; }
/* The question itself carries real item data — names, systems,
   dates — so it is long AND untrusted. Escaped in the builder;
   `pre-wrap` keeps the line breaks the escape would flatten. */
.run-q-ask {
  margin: 0 0 6px; font-weight: 700;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.run-q-opts { display: flex; flex-direction: column; gap: 3px; }
.run-q-opt {
  display: flex; align-items: flex-start; gap: 7px;
  padding: 3px 0; cursor: pointer;
}
.run-q-opt input { margin-top: 2px; flex: none; }
/* Built always, hidden until `Other` is picked — a box created
   on demand cannot be focused in the same tick, and the tier-3
   rationale is the answer people actually type. */
.run-q-other { display: block; margin: 6px 0 0 24px; }
.run-q-other span {
  display: block; font-size: 11px; font-weight: 700;
  color: var(--text-secondary); margin-bottom: 3px;
}
.run-q-other textarea {
  width: 100%; box-sizing: border-box; font: inherit;
  padding: 5px 7px; border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface); color: var(--text-primary);
  resize: vertical;
}
.run-question-note {
  margin: 10px 0 0; color: var(--text-secondary);
  font-size: 11px;
}
/* ONE submit for the set. The reason it is disabled is said
   beside it rather than after the round trip — with five
   questions on screen, "incomplete" is not an instruction. */
.run-q-commit {
  display: flex; align-items: center; gap: 9px;
  margin: 10px 0 0; flex-wrap: wrap;
}
.run-q-why { font-size: 11px; color: var(--text-secondary); }
.run-q-say { margin: 7px 0 0; font-size: 12px; min-height: 1em; }
.run-q-say.err { color: var(--danger); }

.run-approval {
  background: var(--surface-subtle);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--radius-panel);
  padding: 11px 13px; margin: 12px 0; font-size: 13px;
}
.run-approval b { color: var(--text-primary); }
.run-approval-q { margin: 5px 0 0; font-weight: 700; }
/* vagent's own summary text. Escaped, never markdown-rendered
   — the engine's string is attacker-influenced and the
   reference implementation's markdown path executes an
   `onerror` (plan §5.4). `pre-wrap` keeps the line breaks the
   escape would otherwise flatten. */
.run-approval-summary {
  margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere;
  color: var(--text-secondary);
}
.run-approval-note { margin: 8px 0 0; font-size: 12px; }
.run-approval-opts {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px;
}
/* The engine chooses these labels, so they can be long, and a
   button that clips its own label is one nobody can answer
   safely. */
.run-approval-opts .btn { max-width: 100%; white-space: normal; }
.run-approval-say { margin: 8px 0 0; font-size: 12px; min-height: 16px; }
.run-approval-say.err { color: var(--danger-ink); font-weight: 700; }

/* ---- the same decision, once it is history ------------------
   Deliberately NOT the warn palette. `--warn` says "this needs
   you"; an answered decision needs nobody, and leaving it amber
   would make a run with six settled decisions read as six
   things still on fire. Neutral surface, and the outcome is the
   only thing carrying weight. */
.run-decided {
  background: var(--surface-subtle);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--radius-panel);
  padding: 10px 12px; margin: 8px 0; font-size: 13px;
}
.run-decided-q { margin: 5px 0 0; font-weight: 700; }
.run-decided-pick { margin: 6px 0 0; }
/* Same escape-not-markdown reasoning as `.run-approval-summary`
   above: an agent wrote this text. */
.run-decided-summary {
  margin: 6px 0 0; white-space: pre-wrap; overflow-wrap: anywhere;
  color: var(--text-secondary);
}

/* ---- The frozen-run stack (E-45) ----------------------------
   The surface of last resort. The Copilot shelf is closed by
   default (§9), so the common case is that no surface holding an
   approval is on screen at all — which is how live run
   `8d19ea5e` ended with the pause reaching nobody.

   NON-MODAL, by product-owner decision 2026-08-30. It floats; it
   does not cover, block or inert the page, and nothing in
   `agent/frozen.js` moves focus. The stylesheet's part of that
   contract is the z-index below.

   Z-INDEX 890, AND THE NUMBER IS AN ARGUMENT. `.cc-modal` is
   1000 and `.drawer` / `.nav-flyout` are 900. Both of those are
   surfaces a person DELIBERATELY OPENED, and a card that covers
   what somebody just asked for is precisely the interruption
   this design refuses — so the stack goes under them, not over.
   It also means `overlayFocus({modal:true})`'s `isolate()`
   inerts this stack while a dialog is up, which is rule 2 of the
   precedence rule and costs no code.

   No token is invented here.

   CENTRE SCREEN SINCE 2026-09-10, and only its POSITION moved.
   Product-owner direction: the surface that appears when the
   reader is not on the Supervisor tab should be the same
   centre-screen dialog the rail's "read the full request"
   opens, rather than a card in the corner.

   **Still non-modal, and that is why this is CSS alone.** No
   scrim, no focus move, nothing inerted, and `agent/frozen.js`
   is untouched — so every safety behaviour it owns survives
   verbatim: BUSY never destroys a panel mid-answer, "Not now"
   collapses to the chip rather than dismissing, and the stack
   removes itself only when nothing is held. Centring a
   non-modal surface is a far smaller claim than making it
   modal, which the 2026-08-30 decision refused and which this
   does not revisit. */
.frozen-stack {
  position: fixed; z-index: 890;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: min(760px, calc(100vw - 48px));
  max-height: calc(100vh - 64px); overflow-y: auto;
  display: flex; flex-direction: column; align-items: stretch;
  /* ONE PANEL, NOT A PILE OF FLOATING CARDS. It used to be a
     column of shadowed cards in the corner, each carrying its
     own border and tint; centred, that read as clutter rather
     than as a dialog. So the container is the surface — the
     same tokens `.cc-modal` and every other panel in cb-app
     use — and the cards inside it lose their chrome. */
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-overlay);
}
/* ---- the header: what this is, and the way out -------------
   vagent's shape, which is what was asked for: the title on the
   left, a close control in the top right. */
.frozen-head {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 10px;
}
.frozen-title {
  flex: 1 1 auto; min-width: 0;
  font-size: 13px; font-weight: 700;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}
/* Chrome, deliberately quiet: it sits beside a decision, and a
   close button with any visual weight competes with the answer
   buttons below it. */
.frozen-hide { flex: 0 0 auto; font-size: 18px; line-height: 1; }
/* THE PANEL IS THE CARD.

   (The amber this comment used to be about is gone from the
   builder itself now — see `.run-approval` above — so what is
   left here is only the box-in-box reset.) A single approval therefore has no
   box inside a box: the run-approval drops its own background,
   border, padding and shadow entirely, and two of them are
   separated by a rule rather than by two floating panels. */
.frozen-stack .run-approval {
  margin: 0; padding: 0;
  background: none; border: 0; box-shadow: none;
}
/* The dialog header already says "1 run is paused for your
   decision"; the card's own heading would say it twice — and
   it is the last thing carrying `--warn-ink`. */
.frozen-stack .run-approval > b { display: none; }
/* The question stays: with several waiting it is the only thing
   that says WHICH decision this is. */
.frozen-stack .run-approval-q { margin: 0; }
.frozen-stack .frozen-card + .frozen-card {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--line);
}
.frozen-card { display: block; }
.frozen-more {
  margin: 10px 0 0; padding: 7px 9px; font-size: 12px;
  color: var(--text-secondary);
  background: var(--surface-subtle); border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* ---- Channel 4 in the Copilot shelf (E-44) ------------------
   Pinned between the transcript and the input. The log scrolls
   and is rebuilt by the chat channel; a question that scrolls
   away is unanswerable in practice. Empty until something
   freezes, and `:empty` is what keeps it from reserving space
   in a 384px column for the ~99% of the time nothing is. */
.asst-frozen { flex: 0 0 auto; padding: 0 12px; }
.asst-frozen:empty { display: none; }
.asst-frozen .run-approval { margin: 0 0 8px; }
/* WHICH run this is about. E-44's stated hazard is answering
   for the wrong one, and the Copilot is the only surface where
   the card names nothing by itself. */
.asst-frozen-where {
  margin: 8px 0 0; font-size: 12px; font-weight: 700;
  color: var(--text-secondary);
  overflow-wrap: anywhere;
}


/* ===================== DASHBOARD ROLLUPS =====================
   T2 over T3 — every number here is computed on read, so there
   is no table behind these and nothing to drift. */

/* The MW spotlight. Two levels, two tones DELIBERATELY: a
   significant deficiency is worth knowing about; a material
   weakness will actually stop a certification (ADR-032). One
   tone for both would either under-warn or cry wolf. */
.spotlight {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px;
  background: var(--warn-tint);
  border: 1px solid var(--warn-line); border-left: 4px solid var(--warn);
  border-radius: var(--radius-panel);
  padding: 11px 13px; margin: 0 0 14px; font-size: 13px;
}
.spotlight.blocking {
  background: var(--danger-tint); border-color: var(--danger-line);
  border-left-color: var(--danger);
}
.spotlight b { color: var(--warn-ink); }
.spotlight.blocking b { color: var(--danger-ink); }
.spot-n {
  background: var(--surface); border-radius: 999px; padding: 1px 9px;
  font-weight: 700; font-variant-numeric: tabular-nums;
}
.spot-ids { color: var(--text-secondary); font-size: 12px; }
.spotlight p { width: 100%; margin: 0; color: var(--text-secondary);
  font-size: 12px; }

/* Aging. The COUNT comes before the bar in source order, so with
   the stylesheet off this still reads as a list of numbers
   rather than a row of empty boxes (D-21). */
.aging { list-style: none; margin: 0; padding: 0; }
.aging li {
  display: grid; grid-template-columns: 96px 44px minmax(0, 1fr);
  align-items: center; gap: 10px; padding: 3px 0; font-size: 13px;
}
.age-lbl { color: var(--text-secondary); }
.age-n { font-weight: 700; font-variant-numeric: tabular-nums;
  text-align: right; }
.age-bar {
  position: relative; height: 8px; border-radius: 999px;
  background: var(--line);
}
.age-bar::before {
  content: ""; position: absolute; inset: 0 auto 0 0;
  width: var(--w, 0); border-radius: 999px; background: var(--brand);
}

/* A progress meter. Never the only statement of progress — the
   percentage is written next to it. */
.meter { height: 8px; background: var(--line); border-radius: 999px;
  overflow: hidden; }
/* The fill's width is a PARAMETER the view sets as `--w`, the
   same pattern as the aging bars — not a style rule in JS. */
.meter > span { display: block; height: 100%; width: var(--w, 0);
  background: var(--success); }
.meter.warn > span { background: var(--warn); }
.meter.bad > span { background: var(--danger); }
.meter-lbl { margin-top: 6px; font-size: 12px; color: var(--text-secondary); }

/* ===================== RCM AND DEFICIENCIES ===================== */
/* RCM, grouped by business process. `h2` per process because the
   matrix is a list of processes, and a screen-reader user should
   be able to jump between them. */
.rcm-proc { font-size: 16px; line-height: 24px; margin: 0 0 2px; }
/* An uncovered risk is a FINDING, so it is marked in the row
   rather than left as an empty cell the reader must notice. Not
   colour alone — the cell says so in words (WCAG 1.4.1).
   `--danger-ink`, not `--danger`: the latter is the fill used for
   badges and borders and does not carry AA as body text on
   `--surface`. Measured, not assumed. */
tr.rcm-uncovered > th[scope="row"] { color: var(--danger-ink); }
.rcm-gap { margin: 0; color: var(--danger-ink); font-weight: 700; }

/* The ten-stage table in the drawer. `stage-here` marks where the
   record actually is — with CSS off, the view still prints
   "← here" as text next to it. */
.stages th[scope="row"] {
  font-weight: 500; white-space: nowrap;
  color: var(--text-secondary); width: 38%;
}
.stages tr.stage-here th, .stages tr.stage-here td {
  background: var(--brand-tint); font-weight: 700;
  color: var(--text-primary);
}

/* Name/value pairs about one subject (views/profile.js). A <dl>
   rather than a table: these are not rows of comparable
   entities, and table() gives every row a drawer that opens
   nothing. */
.kv-list {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 8px 24px; margin: 0 0 16px; font-size: 13px;
}
.kv-list dt { color: var(--text-secondary); }
.kv-list dd { margin: 0; font-weight: 600; overflow-wrap: anywhere; }
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 4px 0 14px; }
.chips .chip { cursor: default; }
.chips .chip:hover { border-color: var(--line-strong); color: var(--text-secondary); }

/* ===================== `.md` — ONE rendered-markdown style ====
   `06` b6: one table pattern, one drawer, one empty state — and
   one way rendered markdown looks.

   **This class is not applied by hand.** `setMarkdown()` and
   `htmlMarkdown()` in [agent/chat.js](agent/chat.js) set it, and
   they are the only way to render markdown — a lint in
   core/escape.test.js fails the build if a call site reaches past
   them to the raw renderer.

   That indirection is the point. Rendering and STYLING used to be
   separate decisions, and the styling half was forgotten on every
   new surface: the run log, the prepared package, and the Copilot
   bubbles each had rules for `p` and `code` and none for `table`,
   so a findings table parsed correctly and drew as borderless
   rows that read as plain text. Three surfaces, one bug report
   each, same four words every time. Binding the class to the
   render makes the fourth surface impossible. */
.md { overflow-x: auto; }
.md > :first-child { margin-top: 0; }
.md > :last-child { margin-bottom: 0; }
.md p { margin: 0 0 8px; }
.md ul, .md ol { margin: 4px 0 8px; padding-left: 20px; }
.md li { margin: 2px 0; }
.md table {
  border-collapse: collapse; margin: 6px 0; font-size: 12px;
  background: var(--surface);
}
.md th, .md td {
  border: 1px solid var(--line); padding: 4px 8px;
  text-align: left; vertical-align: top;
}
.md th { background: var(--surface-subtle); font-weight: 700; white-space: nowrap; }
/* Count and amount columns line up only with tabular figures. */
.md td:not(:first-child) { font-variant-numeric: tabular-nums; }
/* ---- the handoff block, folded away ----
   A skill's handoff block is JSON addressed to the machine, so
   the renderer puts it behind a core/collapse.js disclosure —
   the same primitive Company Setup uses, not a second one. What
   is here is only its proportions in this context: that
   component is sized for a form section with a 14px bold title,
   and at that weight one collapsed line outshouts the sentence
   above it. Tokens only; nothing below invents a colour. */
/* CLOSED, IT IS ONE LINE. The base component is a panel — a
   bordered, filled, full-width box — which is right for a form
   section and wrong for a footnote under a sentence. A real
   five-stage turn puts five of them in the column, and at 40px
   each that is 200px of chrome carrying one word apiece, more
   weight than the sentences they belong to. Measured 2026-09-17
   in a browser at 420px, which is how it was found.

   So closed, it is an inline trigger at the text's own scale
   and nothing else: no fill, no border, no full-width row. The
   panel comes back when it is OPEN, where it has a body to
   frame. Nothing here changes the disclosure's mechanics —
   `[hidden]` still removes the region from the page and from
   the tab order. */
.md .cl {
  margin: 2px 0; background: none; border: 0;
  border-radius: var(--radius);
}
.md .cl-trigger {
  display: inline-flex; width: auto; gap: 4px;
  padding: 1px 5px 1px 2px; border-radius: 4px;
}
.md .cl.open {
  background: var(--surface-subtle); margin: 6px 0;
}
.md .cl.open > .cl-head .cl-trigger {
  display: flex; width: 100%; padding: 4px 8px; gap: 6px;
}
.md .cl-title {
  font-size: 11px; font-weight: 600; color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: .04em;
}
.md .cl-region { padding: 0 8px 8px; }
/* The block itself: a monospace column that scrolls on its own
   rather than stretching the bubble it sits in. */
/* OPEN, IT IS A WINDOW, NOT A WALL. A real block runs to ~900px
   of wrapped text — measured 895 for one `evaluate` stage with 7
   attributes and 9 artifacts — and at the old 320px cap an
   opened fold took 364px of a 420px column for machine text a
   person opened to check one field. 150px is enough to see what
   kind of thing it is and to scroll to the field; the fold is
   what makes the rest available, and nobody reads a JSON object
   top to bottom in a rail. */
.md-handoff {
  margin: 0; max-height: 150px; overflow: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 6px 8px;
}
/* `pre-wrap`, DECLARED. A handoff block is one very long line —
   `artifact_ids` and `attribute_ids` are arrays — and a rail
   440px wide would otherwise scroll it sideways to read a
   value. It wrapped anyway, off `.exec-node-out`'s inherited
   `overflow-wrap`, which is the right result arrived at by
   accident on one of the surfaces that hosts this. */
.md-handoff code {
  white-space: pre-wrap; overflow-wrap: anywhere;
  font-size: 11px; background: none; padding: 0;
  color: var(--text-secondary);
}
/* ---- a control attribute id, marked where it is written ----
   ADR-153 gives an attribute id an exact shape — three letters
   from eleven, a hyphen, three digits — so this mark is a
   decision and not a pattern that mostly works
   ([core/attributes.js](core/attributes.js)). It says one
   thing: this token is a control attribute. Whether a step
   ADVANCED one is the chip row below, which is the step's own
   statement, and the two must not read alike.

   §4: meaning, not decoration. It is the same brand tint the
   log's attribute chip uses, at prose weight, so the id reads
   as an identifier without stopping the sentence. Tokens only;
   nothing here invents a colour. */
.md-attr {
  font-family: var(--font-mono); font-size: .92em;
  background: var(--brand-tint); color: var(--brand-dark);
  border-radius: 4px; padding: 0 3px;
  /* NOWRAP. Seen at 420px on 2026-09-21: `TML-001` broke after
     the hyphen and read as `TML-` / `001` on two lines. An
     identifier split in half is worse than a ragged right
     edge, and the id is seven characters — it can always fit
     on the next line whole. */
  white-space: nowrap;
}
/* The ids a skill's handoff block says its step advanced,
   above the folded block itself. Deliberately the same
   vocabulary as the execution log's `.lp-chip-attr` — one
   person reads both surfaces about one run, and a second
   shape for one fact is how they stop matching. */
.md-attrs {
  display: flex; flex-wrap: wrap; gap: 4px;
  align-items: center; margin: 6px 0 0;
}
.md-attrs-lbl {
  font-size: 11px; font-weight: 600; color: var(--text-secondary);
  text-transform: uppercase; letter-spacing: .04em;
}
/* ONE BOX PER ATTRIBUTE: the code, what it claims, and what is
   known about it. A row of bare codes was legible only to
   somebody holding the ontology in their head — `CMP-001` is
   not a description, and the description is one fetch away in
   the control's own config.

   The box takes its TONE from the stage verdict so the group
   reads as one statement, and a left border rather than a full
   fill: several of these stack under a sentence, and five
   filled blocks would outweigh the prose they annotate. */
.md-attr-box {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 4px 8px; margin: 3px 0; padding: 5px 9px;
  border-left: 3px solid var(--line-strong);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--surface-subtle);
}
.md-attr-box.ok {
  border-left-color: var(--success-ink);
  background: var(--success-tint);
}
.md-attr-box.exception {
  border-left-color: var(--danger-ink);
  background: var(--danger-tint);
}
.md-attr-box.requires_review {
  border-left-color: var(--warn-ink); background: var(--warn-tint);
}
/* THE RUN'S OWN STATES, which beat the stage verdict wherever
   a run is known. Same five as screen 05's coverage matrix and
   the same tones, because one state must not look different on
   two surfaces: satisfied green; qualified and pending amber —
   both mean *not settled*, and drawing pending as neutral is how
   an unfinished attribute reads as a finished one; gap red,
   because it IS the finding; `na` muted, an exclusion the
   archetype made and not an outcome. */
.md-attr-box.satisfied {
  border-left-color: var(--success-ink);
  background: var(--success-tint);
}
.md-attr-box.satisfied .md-attr-state { color: var(--success-ink); }
.md-attr-box.qualified,
.md-attr-box.pending {
  border-left-color: var(--warn-ink); background: var(--warn-tint);
}
.md-attr-box.qualified .md-attr-state,
.md-attr-box.pending .md-attr-state { color: var(--warn-ink); }
.md-attr-box.gap {
  border-left-color: var(--danger-ink);
  background: var(--danger-tint);
}
.md-attr-box.gap .md-attr-state { color: var(--danger-ink); }
/* Muted, not green. An attribute this control's archetype
   excludes was never owed, and dressing it as satisfied
   inflates the coverage a reader counts. */
.md-attr-box.na {
  border-left-color: var(--line-strong);
  background: var(--surface-subtle);
}
.md-attr-box.na .md-attr-state { color: var(--text-secondary); }
/* The one PER-ATTRIBUTE fact the browser can establish mid-turn,
   so it overrides the group's tone rather than inheriting it:
   this control's config does not declare this id. Until the
   config lands nothing is claimed either way — "not known yet"
   and "not declared" look identical and mean opposite things. */
.md-attr-box.undeclared {
  border-left-color: var(--warn-ink); background: var(--warn-tint);
}
.md-attr-code {
  font-family: var(--font-mono); font-size: 11px;
  font-weight: 700; color: var(--text-primary);
}
.md-attr-name {
  flex: 1; min-width: 0; font-size: 12px;
  color: var(--text-primary);
}
/* The status, on the box, in the box's own ink. Green
   effective, amber needs review, red exception — the run's own
   three words, so the chat and the execution log do not name
   the same state differently. It reads as a label rather than
   a second pill: the box is already carrying the colour, and a
   tinted pill inside a tinted box is two shapes for one fact. */
.md-attr-state {
  margin-left: auto; font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--text-secondary);
}
.md-attr-box.ok .md-attr-state { color: var(--success-ink); }
.md-attr-box.exception .md-attr-state { color: var(--danger-ink); }
.md-attr-box.requires_review .md-attr-state,
.md-attr-box.undeclared .md-attr-state { color: var(--warn-ink); }
/* Headings inside rendered output are content, not page chrome —
   they must not inherit the view's heading scale. */
.md h1, .md h2, .md h3 {
  font-size: 13px; font-weight: 700; margin: 10px 0 4px;
  text-transform: none; letter-spacing: 0; color: var(--text-primary);
}
.md code {
  background: var(--surface-subtle); border: 1px solid var(--line);
  padding: 0 4px; border-radius: 4px; font-size: 12px;
  font-family: var(--font-mono);
}
.md pre {
  background: var(--surface-subtle); border: 1px solid var(--line);
  padding: 9px 11px; border-radius: var(--radius);
  overflow-x: auto; margin: 6px 0;
}
.md pre code { background: none; border: 0; padding: 0; }
.md blockquote {
  margin: 6px 0; padding: 4px 10px;
  border-left: 3px solid var(--line); color: var(--text-secondary);
}
.md hr { border: 0; border-top: 1px solid var(--line); margin: 10px 0; }
.md a { color: var(--brand-dark); }
/* A wide findings table inside a drawer scrolls rather than
   clipping — the drawer is the narrowest surface `.md` lands on,
   and a clipped table silently hides columns. */
.drawer .md table { min-width: 100%; width: max-content; }

/* Inline error text, replacing a style="" attribute that the
   no-styles-in-JS rule forbids. */
.bad { color: var(--danger-ink); }

/* ===================== SIGN-IN =====================
   `09` §7. Shown only when the server says authentication is
   required and there is no session. Hiding it is not a control —
   the server returns 401 regardless. */
.signin {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  /* Flat --background, not the former three-stop gradient: §4
     admits no gradients anywhere, and "the sign-in page is the
     one screen with nothing else on it" was an argument for
     decorating the exception rather than for keeping the rule. */
  background: var(--background);
}
.signin[hidden] { display: none; }
.signin-card {
  display: flex; flex-direction: column;
  width: 360px; max-width: 100%; padding: 26px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-panel);
}
.signin-brand { text-align: center; margin-bottom: 18px; }
/* The vertical brand lockup — mark above the "ControlBridge.ai"
   wordmark. Width is fixed and height auto so the vendor file's
   own 792x540 ratio governs; no border-radius, because the
   asset is not a tile and rounding it would be an alteration.
   Replaces the former .signin-mark (44px app tile) + .signin-name
   pair, whose text repeated the wordmark. */
/* No margin of its own. The vendor file carries 116.5 of its 540
   viewBox units as padding above and below the ink (measured with
   getBBox: ink is 660x307 at x=66, y=116.5), which is the logo's
   clearspace — at 200px wide that is ~29px top and bottom
   already. Adding margin on top of it double-counts and opens a
   gap that reads as a layout bug rather than as breathing room. */
.signin-logo { width: 200px; height: auto; display: inline-block; }
/* Pulled up into the lockup's own bottom padding. The vendor
   file carries ~29px of it at this width, which is right for a
   logo standing alone and too much between a logo and the line
   that belongs to it — the tagline read as a separate block
   rather than as part of the brand. -12px leaves ~17px, so the
   mark still has clear space; it is the file's built-in padding
   being spent, not the brand's clearspace rule being broken.
   That rule is in the 181 MB guidelines PDF, which is not
   committable and states no number we can check against. */
.signin-tagline {
  margin: -12px 0 0; font-size: 13px; font-weight: 600;
  letter-spacing: -.01em; color: var(--brand-mark);
}
.signin-panel { display: flex; flex-direction: column; gap: 6px; }
.signin-panel[hidden] { display: none; }
.signin-h {
  margin: 0 0 10px; font-size: 16px; line-height: 24px; font-weight: 700;
}
.signin-help {
  margin: 0 0 6px; font-size: 13px; line-height: 1.45;
  color: var(--text-secondary);
}
.signin-card label {
  font-size: 12px; font-weight: 600; color: var(--text-secondary);
  margin-top: 8px;
}
/* Label and "Forgot Password?" share a row. baseline so the
   small link sits on the label's text line rather than
   floating. */
.signin-labelrow {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px;
}
.signin-labelrow label { margin-top: 8px; }
.signin-card input {
  min-height: 36px; padding: 8px 11px; font-size: 14px;
  color: var(--text-primary); background: var(--surface);
  border: 1px solid var(--line-strong); border-radius: var(--radius);
}
.signin-card input::placeholder { color: var(--text-muted); }
/* 8px, not 16: .signin-error above already reserves its height so
   an error does not shove the button down, and 16 on top of that
   reserved row read as a gap rather than as spacing. */
.signin-card button[type="submit"] {
  margin-top: 8px; min-height: 40px; padding: 9px 12px;
  font-size: 14px; font-weight: 700;
  color: var(--surface); background: var(--brand);
  border: 1px solid transparent; border-radius: var(--radius);
  cursor: pointer;
}
.signin-card button[type="submit"]:hover { background: var(--brand-dark); }
.signin-card button[type="submit"]:disabled { opacity: .6; cursor: default; }
/* A text button that reads as a link but switches panel. Kept a
   real <button> for the keyboard and the screen reader; styled
   down so it does not compete with the submit. */
.signin-link {
  padding: 0; border: 0; background: none; cursor: pointer;
  font-size: 12px; font-family: inherit; color: var(--brand-dark);
}
.signin-link:hover { text-decoration: underline; }
.signin-back { margin-top: 12px; align-self: center; }
.signin-error {
  margin: 8px 0 0; min-height: 16px; font-size: 12px;
  color: var(--danger-ink);
}
/* The success case reuses .signin-error's slot — same
   role="alert", so the message is announced either way. The
   colour is not the only difference: the copy states the
   outcome. */
.signin-error.ok { color: var(--success-ink); }
.signin-foot {
  margin: 18px 0 0; padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: 12px; line-height: 1.45; text-align: center;
  color: var(--text-secondary);
}

/* ===================== FILE DROP =====================
   core/filedrop.js. The zone is a <label> wrapping a real
   `<input type="file" multiple>`, so everything below is styling
   a native control rather than replacing one: the keyboard path,
   the picker and the AT announcement are the platform's.

   The input is moved off-screen rather than `display: none`,
   because a hidden input is not focusable and the label would
   then be unreachable by keyboard — the exact failure §14 is
   about. `.sr-only`'s technique, applied for the same reason. */
.drop-input {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.drop { margin: 0 0 16px; }
.drop-target {
  display: flex; flex-direction: column; align-items: center;
  gap: 4px; padding: 28px 20px; cursor: pointer; text-align: center;
  background: var(--surface-subtle);
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius-panel);
  transition: background var(--t-fast) ease-out,
    border-color var(--t-fast) ease-out;
}
.drop-target:hover {
  border-color: var(--brand); background: var(--brand-tint);
}
/* Focus lands on the INPUT, which is off-screen, so the ring has
   to be drawn on the label it belongs to — otherwise a keyboard
   user tabs onto an invisible target with no visible focus at
   all. */
.drop-input:focus-visible + .drop-icon,
.drop-target:focus-within {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
/* Dragging. Not colour alone: the border also goes solid, so the
   state survives a monochrome or high-contrast rendering (§14). */
.drop-target.is-over {
  border-style: solid; border-color: var(--brand);
  background: var(--brand-tint);
}
.drop-icon { color: var(--brand-dark); line-height: 0; }
.drop-icon svg { width: 24px; height: 24px; }
.drop-lead { font-size: 14px; font-weight: 700; }
.drop-hint {
  font-size: 12px; line-height: 1.45;
  color: var(--text-secondary); max-width: 46ch;
}
/* The queue. One row per file, and the note is where the outcome
   is stated in WORDS — a row that only changed colour would say
   nothing to a screen reader, which is why the list is
   aria-live. */
.drop-list { list-style: none; margin: 12px 0 0; padding: 0; }
.drop-item {
  display: flex; align-items: baseline; gap: 8px;
  padding: 7px 10px; font-size: 13px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.drop-item + .drop-item { margin-top: 6px; }
.drop-item-name {
  font-weight: 600; overflow-wrap: anywhere; flex: 1 1 auto;
}
.drop-item-size {
  color: var(--text-secondary); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.drop-item-note { color: var(--text-secondary); white-space: nowrap; }
.drop-item.is-done {
  border-color: var(--success-line); background: var(--success-tint);
}
.drop-item.is-done .drop-item-note { color: var(--success-ink); }
.drop-item.is-refused {
  border-color: var(--danger-line); background: var(--danger-tint);
}
.drop-item.is-refused .drop-item-note {
  color: var(--danger-ink); font-weight: 600; white-space: normal;
}
.drop-item.is-sending { border-color: var(--brand-line); }

/* Company Setup's document corpus. It uses the shared table
   contract, with only the column geometry and caption hierarchy
   specialized for this workspace. */
.documents-workspace .state { margin-bottom: 16px; }
.documents-workspace .drop-target { padding-block: 20px; }
.doc-table { table-layout: fixed; margin: 24px 0; }
.doc-table caption {
  color: var(--text-primary); font-size: 16px; font-weight: 700;
}
.doc-count {
  float: right;
  color: var(--text-secondary); font-size: 12px; font-weight: 600;
  white-space: nowrap;
}
.doc-table th:first-child { width: 48%; }
.doc-table th:nth-child(2) { width: 16%; }
.doc-table th:nth-child(3) { width: 26%; }
.doc-table th:last-child { width: 10%; }
.doc-table tbody th, .doc-table tbody td { vertical-align: middle; }
.doc-name { display: block; overflow-wrap: anywhere; }
.doc-primary {
  display: flex; align-items: flex-start; flex-wrap: wrap; gap: 6px 8px;
}
.doc-identity { min-width: 0; }
.doc-primary .doc-name { min-width: 0; }
.source-kind { display: block; font-weight: 600; }
.doc-remove { min-width: 36px; padding-inline: 8px; }
.doc-remove svg { width: 16px; height: 16px; }
button.btn.quiet.doc-remove { color: var(--text-secondary); }
button.btn.quiet.doc-remove:hover {
  color: var(--danger-ink); background: var(--danger-tint);
}

/* EDGAR is one retrieval lane: status, action and repeated records
   stay together instead of reading as three unrelated blocks. */
.filing-lane { margin-top: 24px; }
.filing-lane .doc-head { margin: 0 0 4px; }
.filing-copy { max-width: 76ch; margin: 0 0 12px; }
.filing-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-height: 44px; padding: 7px 10px;
  background: var(--surface-subtle); border: 1px solid var(--line);
  border-radius: var(--radius-panel);
}
.filing-counts { display: flex; align-items: center; gap: 16px; }
.filing-count {
  color: var(--text-secondary); font-size: 12px; white-space: nowrap;
}
.filing-count b {
  color: var(--text-primary); font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.filing-count.success b { color: var(--success-ink); }
.filing-count.danger b { color: var(--danger-ink); }
.filing-source {
  display: inline-flex; align-items: center;
  gap: 6px; min-height: 32px;
}
.filing-source svg { width: 15px; height: 15px; }
.docs-next {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; margin-top: 24px; padding-top: 16px;
  border-top: 1px solid var(--line);
}
.docs-next h3 { margin: 0 0 2px; font-size: 14px; }
.docs-next p { margin: 0; color: var(--text-secondary); font-size: 13px; }
.docs-next .btn { flex: none; }

/* A list that is a list semantically and reads as prose. Used for
   the permitted storage roots on Settings, where the bullets add
   nothing and the paths are the content. */
ul.plain { list-style: none; margin: 6px 0 0; padding: 0; }
ul.plain li { font-size: 12px; overflow-wrap: anywhere; }

/* ===================== RESPONSIVE =====================
   §11: the interaction MODEL changes; the desktop layout is not
   merely shrunk.

   Shell preferences degrade rather than being overwritten. The
   `nav-rail` / `copilot-open` classes and their stored
   preferences are left alone; these rules override the WIDTH
   they resolve to, so a desktop preference is still there when
   the window widens again.

   **Nothing here hides text inside `#view` with `display:none`.**
   view_check.py reads every view twice — with the stylesheet and
   without it — and fails if the words or their order change. A
   pane that must disappear at narrow widths is therefore given
   the `hidden` ATTRIBUTE by JS ([core/responsive.js](core/responsive.js)),
   which survives the stylesheet being removed. */

/* 960–1279: the icon rail is preferred and supporting panels
   become overlays. */
@media (max-width: 1279px) {
  /* The rail's WIDTH is no longer set here — `nav-rail-auto`
     carries it, so the width and the presentation cannot
     disagree. See the note beside that class. */
  .layout.copilot-open { --copilot-rail: min(var(--copilot-w), 45vw); }
}

/* Below 960 the Copilot stops being a column: side by side would
   push the work area under its usable minimum (§9). */
@media (max-width: 959px) {
  :root { --gutter: 16px; }
  .layout { grid-template-columns: var(--nav-w) minmax(0, 1fr); }
  .assistant { display: none; }
  .layout.copilot-open .assistant {
    position: fixed; top: var(--topbar-h); right: 0; bottom: 0;
    z-index: 800; display: flex;
    width: min(420px, 100vw); border-left: 1px solid var(--line-strong);
    box-shadow: var(--shadow-overlay);
  }
  .layout.copilot-open .asst-resizer { display: none; }
  .workspace, .workspace.mirrored { grid-template-columns: minmax(0, 1fr); }
  .pane.queue-pane { border-right: 0; border-bottom: 1px solid var(--line); }
  .wf-node { grid-template-columns: minmax(0, 1fr); gap: 6px; }

  /* The document ledger has four evidence-heavy columns. Switch
     it to compact records with the workspace at the documented
     960px mode change instead of squeezing the desktop geometry. */
  table.doc-table[data-records] thead { display: none; }
  table.doc-table[data-records],
  table.doc-table[data-records] tbody,
  table.doc-table[data-records] th,
  table.doc-table[data-records] td { display: block; width: auto; }
  table.doc-table[data-records] { border: 0; }
  table.doc-table[data-records] tr {
    position: relative; display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    margin-bottom: 8px; padding: 4px 0 6px;
    background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--radius-panel);
  }
  table.doc-table[data-records] th,
  table.doc-table[data-records] td {
    border-bottom: 0; padding: 6px 12px;
  }
  table.doc-table[data-records] tbody th[scope="row"] {
    grid-column: 1 / -1; padding-right: 56px;
    font-size: 14px; white-space: normal;
    border-bottom: 1px solid var(--line);
  }
  table.doc-table[data-records] td[data-label]::before {
    content: attr(data-label);
    display: block; color: var(--text-secondary);
    font-size: 12px; font-weight: 600;
  }
  table.doc-table[data-records] td:nth-of-type(1) { grid-column: 1; }
  table.doc-table[data-records] td:nth-of-type(2) { grid-column: 1 / -1; }
  table.doc-table[data-records] td.row-act {
    position: absolute; top: 4px; right: 4px;
    width: 44px; padding: 0;
  }
  table.doc-table[data-records] td.row-act::before { content: none; }
  .doc-table caption { font-size: 16px; }
  .doc-count { float: none; display: block; margin-top: 2px; }
  .doc-name { display: flex; align-items: center; min-height: 44px; }
  .doc-remove { min-width: 44px; }
}

/* Below 720: mobile task mode. One task at a time, full-width
   controls, 44px touch targets (§11, §14). */
@media (max-width: 719px) {
  /* The utility bar has to hold a nav toggle, the brand, a
     search field and two actions in 390px. Something gives, and
     what gives is the DECORATION: the framework tag and the
     account's role text (both restated inside the app — the tag
     on the sign-in screen, the roles on My Profile), and the
     Copilot button's visible word. The button keeps its
     `aria-label`, so it is still a named target for a screen
     reader AND for the agent, which reads `aria-label` before
     text. Nothing that is only available here is dropped. */
  /* The PROPERTIES move, not just the declarations — anything
     that binds to --tb-pad/--tb-gap must see this block's
     values here, or they would be true at one width and a
     lie at another. */
  .topbar { --tb-pad: 10px; --tb-gap: 8px; }
  .acct { display: none; }
  /* THE MARK STAYS AT 20px DOWN HERE, and this is the "own
     decision" the removed version of this rule said the narrow
     case would need if the default were ever raised again. It
     was raised on 2026-09-09, to vagent's 24px (ADR-154), and
     vagent has no 390px layout to copy — §11 governs instead.

     Measured at 390px with the 24px default inherited: the mark
     is 177.6px wide and the search field is crushed to 72px,
     beside a 44px toggle. At 20px it is 148px and the field gets
     ~102px. Neither is generous; one is usable. The tie to
     `--nav-w` that this block used to release is gone entirely —
     `.brand` has no width and no justify-content to override at
     any breakpoint now, so those two declarations went with it
     rather than staying as a rule that changes nothing. */
  .brand-logo { height: 20px; }
  .search { height: 40px; }
  .search-key { display: none; }
  #asst-toggle > span:not(.ico) { display: none; }
  #asst-toggle { min-width: 44px; padding: 0; }
  /* The nav becomes an off-canvas drawer. It is removed from the
     grid entirely rather than squeezed. */
  .layout { grid-template-columns: minmax(0, 1fr); }
  .sidebar {
    position: fixed; top: var(--topbar-h); bottom: 0; left: 0;
    z-index: 810; width: 268px; max-width: 84vw;
    box-shadow: var(--shadow-overlay);
    transform: translateX(-100%);
    transition: transform var(--t-slow) ease-out;
  }
  .layout.nav-open .sidebar { transform: translateX(0); }
  /* The rail is a desktop affordance only (§8) — at mobile the
     drawer shows full labels. */
  .layout.nav-rail .sidebar,
.layout.nav-rail-auto .sidebar { padding-inline: 8px; }
  .layout.nav-rail .nav-item,
.layout.nav-rail-auto .nav-item {
    justify-content: flex-start; gap: 10px;
    padding: 9px 10px 9px 18px; font-size: 13px;
  }
  /* The flyout label. On the LEAVES and on the GROUP HEADINGS
   alike — §8 requires that a collapsed destination expose its
   full label on hover and on keyboard focus, and a heading
   reduced to a hairline needs it for the same reason. Printed
   from `data-label` so the visible label span can stay in the
   DOM at `font-size: 0`, which is what keeps `cb_adapter`'s
   label for an agent target unchanged by the collapse. */
.layout.nav-rail .nav-item::after,
.layout.nav-rail-auto .nav-item::after,
.layout.nav-rail .nav-head::after,
.layout.nav-rail-auto .nav-head::after { content: none; }
  .layout.nav-rail .nav-head,
.layout.nav-rail-auto .nav-head {
    height: auto; margin: 0; padding: 9px 8px;
    font-size: 12px; background: none;
  }
  .layout.nav-rail .nav-item .badge,
.layout.nav-rail-auto .nav-item .badge {
    position: static; font-size: 12px; padding: 0 7px;
  }
  /* The drawer slides over the work area, so there is no boundary
     to drag and nothing behind the separator but the page. The
     button is the control at this width. */
  .nav-resizer { display: none; }
  .nav-scrim {
    position: fixed; inset: var(--topbar-h) 0 0; z-index: 805;
    background: rgba(15, 23, 42, .45); border: 0;
  }
  .nav-scrim[hidden] { display: none; }
  .layout.copilot-open .assistant {
    width: 100vw; border-left: 0;
  }
  /* Touch targets. 44px is the floor, not the look.
     **`.sm` has to be named explicitly.** `button.btn.sm` is
     (0,2,1) and the bare `button.btn` below is (0,1,1), so the
     base rule's `min-height: 30px` won and every small button —
     Back to the queue, Show config, Show detail, and all three
     Deficiencies actions — measured 30px at 375px wide while
     this block looked like it had covered them. A media query
     adds no specificity; only the selector does. */
  .nav-item, .queue-row, .facet, .chip, .icon-btn,
  button.btn, .cc-pkg-name, .tbl-row-open { min-height: 44px; }
  button.btn.sm, a.btn.sm, button.btn.lg,
  select.filter, input.filter,
  .asst-suggest .sg, .asst-model, .topbar-select,
  .signin-link {
    min-height: 44px;
  }
  /* The two the named list missed, found by the target-size
     assertion in keyboard_check.py rather than by reading this
     block again: a group heading measured 251x33 and the global
     search field 52x38. The search used to be a composite —
     wrapper plus input — and needed the floor on both or the box
     grew around a 38px hit area. It is one button now, so one
     rule reaches the whole hit area. */
  .nav-group > .nav-head, .search {
    min-height: 44px;
  }
  /* The wizard's and the form primitive's controls, which the
     named list above missed for the same reason it missed the
     other two: a block that looks like it covers "controls" only
     covers the selectors it writes down. Measured at 375px before
     this existed — step pills 29px, inputs 35px, selects 33px.

     16px text on every input, not 13px: below 16px iOS Safari
     zooms the viewport on focus, which then leaves the page
     horizontally scrolled and is indistinguishable from a layout
     bug. This is the one place the type scale is overridden
     upwards on purpose. */
  .wiz-step, .cat > summary { min-height: 44px; }
  .field input, .field select, .field textarea,
  .check {
    min-height: 44px; font-size: 16px;
  }
  .field textarea { min-height: 88px; }
  .check { align-items: center; }
  .sw { min-height: 44px; min-width: 44px; }

  /* Below 720px the six step pills cost ~169px of the first
     screen before the task begins. The count and the bar carry
     orientation; the full list becomes a disclosure. */
  .wiz-rail { display: none; }
  .wiz-rail.shown { display: block; }
  .wiz-rail ol { flex-direction: column; }
  .wiz-step { width: 100%; text-align: left; border-radius: var(--radius); }
  /* BLOCK, not flex. The desktop default is `display: none` and the
     first version of this rule turned it into a flex ROW, which put
     the phase number, the phase name, the screen name and the
     button side by side in 375px and cramped all four. The header
     is four stacked lines. */
  .wiz-here { display: block; }

  /* An inline hit area as well as a block one, where the control
     is not already full width. */
  .icon-btn, #asst-toggle, #nav-toggle { min-width: 44px; }
  button.btn { width: 100%; }
  .page-head-actions .btn, .cc-actions .btn, .decision-acts .btn,
  .tbl-actions .btn, .state .btn { width: 100%; }
  .page-head { flex-direction: column; align-items: stretch; gap: 12px; }
  .page-head-actions { flex-direction: column; }
  /* A pane head holding a title and a filter strip has no room
     for both on one line at 390px. */
  .pane-head { flex-direction: column; align-items: stretch; gap: 8px; }
  .decision { grid-template-columns: minmax(0, 1fr); }
  .metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .metric { border-bottom: 1px solid var(--line); }
  .facts { grid-template-columns: minmax(0, 1fr); }
  .fact { border-right: 0; border-bottom: 1px solid var(--line); }
  .fact:last-child { border-bottom: 0; }
  .basis > div { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .drawer { width: 100vw; max-width: 100vw; }
  .cc-modal { padding: 0; }
  .cc-modal-card { max-height: 100vh; border-radius: 0; border: 0; }
  .kv { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
  .kv .k { margin-top: 8px; }

  /* §8: a mobile RECORD representation, not a clipped table.
     Done with generated content from `data-label`, which is the
     one technique that adds no text to the DOM — so D-21's
     stylesheet-off read still gets a plain, correct table with
     its real <thead>, and the styled read gets records.

     **Gated on `[data-records]`, and that is the fix for a real
     defect.** The rule used to apply to every `table.tbl` and
     hide every `<thead>`. `table()` labels its cells, but the
     hand-built tables in `processes.js` and `rcm.js` did not — so
     at 375px a reader saw "1", "Receive customer order",
     "Sales operations" with no column names anywhere, visible or
     programmatic. A global rule that depends on a per-call-site
     contract will outrun that contract the first time someone
     writes a table by hand. Now the table has to *say* it has
     labels before its headers are taken away. */
  table.tbl[data-records] thead { display: none; }
  table.tbl[data-records],
  table.tbl[data-records] tbody, table.tbl[data-records] tr,
  table.tbl[data-records] th,
  table.tbl[data-records] td { display: block; width: auto; }
  table.tbl[data-records] { border: 0; }
  table.tbl[data-records] tr {
    margin-bottom: 8px; padding: 4px 0;
    background: var(--surface);
    border: 1px solid var(--line); border-radius: var(--radius-panel);
  }
  table.tbl[data-records] th,
  table.tbl[data-records] td {
    border-bottom: 0; padding: 6px 12px;
  }
  table.tbl[data-records] tbody th[scope="row"] {
    font-size: 14px; white-space: normal;
    border-bottom: 1px solid var(--line);
  }
  table.tbl[data-records] td[data-label]::before {
    content: attr(data-label);
    display: block; color: var(--text-secondary);
    font-size: 12px; font-weight: 600;
  }
  /* A table without labels keeps its headers and scrolls inside
     its own box rather than losing meaning. Not ideal — §8
     prefers no horizontal scrolling — but a scrollable table a
     reader can interpret beats a stacked one they cannot. */
  table.tbl:not([data-records]) { display: block; overflow-x: auto; }
  table.tbl-fixed { table-layout: auto; }
  .stages th[scope="row"] { width: auto; }
  .documents-workspace .drop-target { padding-block: 20px; }
  table.doc-table[data-records] tr { display: grid; }
  .filing-toolbar { align-items: stretch; flex-direction: column; }
  .filing-counts { justify-content: space-between; gap: 8px; }
  .filing-actions .btn { width: 100%; }
  .filing-source { justify-content: flex-start; min-height: 44px; }
  .docs-next { align-items: stretch; flex-direction: column; }
}

/* ===================== ACCESSIBILITY AND PLATFORM =====================
   Audited against devdocs/migration-steps/ui-guidelines-pinned.md. */

/* Visually hidden but reachable by screen readers and by Tab.
   Not `display:none` — that removes it from the a11y tree. */
.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-link {
  position: absolute; left: 8px; top: -48px; z-index: 1200;
  padding: 8px 14px; border-radius: 0 0 var(--radius) var(--radius);
  background: var(--brand); color: var(--surface);
  text-decoration: none; transition: top var(--t-mid) ease-out;
}
.skip-link:focus { top: 0; }

/* Headings should not leave one word on the last line. */
h1, h2, h3 { text-wrap: balance; }

/* Native select: Windows dark mode renders an unstyled select
   with a dark background and dark text unless both are set. */
select.filter, select {
  background-color: var(--surface); color: var(--text-primary);
}

/* A scroll inside a modal or drawer must not chain to the page
   behind it. */
.drawer, .cc-modal-card, .asst-log { overscroll-behavior: contain; }

/* Removes the 300ms double-tap delay, and makes the tap
   highlight a deliberate choice rather than the OS default. */
button, a, .nav-item, .queue-row, .tbl-row-open, .card {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(37, 99, 235, .15);
}

/* Honour a reduced-motion preference. Not "no animation" —
   near-instant, so state changes are still perceivable. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- The testing calendar (views/calendar.js, M-04) ---------
   A list by month, not a grid of squares: a testing schedule is
   read as "what is due and what has slipped", and a month with
   two entries in a seven-column grid is mostly empty cells.
   Each row is date / control / sample / state, and it degrades
   to a readable line when the stylesheet is gone (D-21) because
   every part carries its own words. */
.cal-list { list-style: none; margin: 0; padding: 0; }
.cal-entry {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr) auto auto;
  align-items: center; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.cal-entry:last-child { border-bottom: 0; }
.cal-when { color: var(--text-secondary); font-size: 12px; }
.cal-what { min-width: 0; overflow-wrap: anywhere; }
.cal-size { color: var(--text-secondary); font-size: 12px; }
.cal-month + .cal-month { margin-top: 12px; }

@media (max-width: 720px) {
  .cal-entry { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}

/* ---- Walkthroughs (views/walkthroughs.js, M-01) -------------
   The question, its rubric and the answer box, stacked. No
   player and no timeline: ADR-034 drops the video pipeline and
   keeps the documented walkthrough, so this surface is prose and
   a text box by design. */
.wt-input {
  display: block; width: 100%; margin: 6px 0 8px;
  padding: 8px 10px; font: inherit;
  color: var(--text-primary); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
  resize: vertical;
}
/* --brand, not --focus: there is no --focus token, and
   every other focus-visible rule here uses --brand. An
   undefined custom property makes `outline` invalid, so
   the ring does not render at all and a keyboard user
   loses the focus indicator silently. */
.wt-input:focus-visible { outline: 2px solid var(--brand);
  outline-offset: 1px; }
/* A frozen answer reads as prose, not as a disabled input: the
   database refuses the write, and a greyed-out box invites the
   attempt. */
.wt-answer {
  margin: 6px 0 4px; padding: 8px 10px;
  background: var(--surface-sunken, var(--surface));
  border-left: 3px solid var(--line); border-radius: 3px;
}
.wt-q + .wt-q { margin-top: 10px; }
.wt-detail { margin: 14px 0 8px; }

/* ---- The trial-balance preview (views/import.js, M-02 f9c) ---
   Five counts in a row, each a number over its label. The
   numbers are what a reader acts on — "12 changed" is the whole
   decision — so they lead, and the label sits under. Wraps to
   one per line under 640px rather than shrinking. */
.tb-counts {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0;
}
.tb-count {
  flex: 1 1 110px; padding: 8px 11px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius-panel);
}
.tb-count b { display: block; font-size: 18px; }
.tb-count span {
  display: block; margin-top: 2px;
  color: var(--text-secondary); font-size: 12px;
}
.tb-preview-box { margin-top: 14px; }

/* ---- view-as banner (ADR-097) ------------------------------
 * Read-only impersonation, announced. Deliberately NOT
 * dismissible: a banner with a close button is invisible the
 * moment somebody closes it, and then a screenshot of a
 * customer's data is indistinguishable from one the customer
 * took. The only control is the one that ends the session.
 *
 * `position: sticky` and not `fixed`: fixed would overlay the
 * first rows of a table on a short viewport, and a banner that
 * hides the data it is warning you about trades one problem for
 * another. */
.viewas-banner {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  gap: .75rem;
  align-items: center;
  justify-content: space-between;
  padding: .5rem var(--gutter);
  background: var(--danger);
  color: var(--danger-ink);
  font-size: .875rem;
  border-bottom: 1px solid rgba(0, 0, 0, .25);
}

.viewas-banner__text { display: flex; gap: .5rem;
  align-items: center; flex-wrap: wrap; }
.viewas-banner__who { font-weight: 600; }
.viewas-banner__sep { opacity: .6; }
.viewas-banner__ro,
.viewas-banner__real { opacity: .9; }

.viewas-banner__stop {
  flex: none;
  padding: .25rem .75rem;
  border: 1px solid currentColor;
  border-radius: var(--radius);
  background: transparent;
  color: inherit;
  cursor: pointer;
  font: inherit;
}
.viewas-banner__stop:hover { background: rgba(255, 255, 255, .15); }
.viewas-banner__stop:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

/* ===================== DIAGRAM WORKSPACE =====================
 * The shared diagram workspace (§3 of
 * [diagrams/DESIGN.md](../../devdocs/design/diagrams/DESIGN.md)),
 * and the `--diagram-*` alias layer §4 declares.
 *
 * **The aliases are `var()` references, never hex.** §4 says why
 * and the check is real: a literal here is a second copy of the
 * palette, and this block had already drifted twice in the doc —
 * `--diagram-node-secondary` carried a superseded value and
 * `--diagram-edge` carried `#64748b`, the value the product
 * palette deliberately rejected for failing AA. Aliasing makes a
 * palette change reach the canvas by itself.
 *
 * No fallback chains. `var(--diagram-node-surface, #fff)` is a
 * third copy of the mapping; if a token is missing it should read
 * as missing. */
:root {
  --diagram-canvas: var(--surface-subtle);
  --diagram-grid: var(--line);
  --diagram-grid-strong: var(--line-strong);

  --diagram-node-surface: var(--surface);
  --diagram-node-subtle: var(--surface-subtle);
  --diagram-node-border: var(--line-strong);
  --diagram-node-text: var(--text-primary);
  --diagram-node-secondary: var(--text-secondary);

  --diagram-edge: var(--text-secondary);
  --diagram-edge-muted: var(--text-muted);
  --diagram-edge-emphasis: var(--brand);

  --diagram-selection: var(--brand);
  --diagram-selection-tint: var(--brand-tint);
  --diagram-focus: var(--brand);

  --diagram-success: var(--success-ink);
  --diagram-success-tint: var(--success-tint);
  --diagram-warning: var(--warn-ink);
  --diagram-warning-tint: var(--warn-tint);
  --diagram-danger: var(--danger-ink);
  --diagram-danger-tint: var(--danger-tint);


  --diagram-marker-risk: var(--marker-risk);
  /* 4.15 against white — BELOW AA's 4.5 — and legitimate only
   * because its glyph is a `<path>`, a graphical object held to
   * §4's 3:1 non-text threshold. Redraw that tick as a text "✓"
   * and this value drops below AA. `design_tokens_test.py`
   * recomputes ratios, so it would go red at the redraw rather
   * than here — which is correct, and is exactly why the reason
   * has to live at the value. A green suite otherwise reads as
   * "the palette is fine". */
  --diagram-marker-control-key: var(--marker-control-key);
  --diagram-marker-control-automated: var(--marker-control-automated);
  --diagram-marker-control-proposed: var(--brand);
  --diagram-marker-control-gap: var(--marker-control-gap);
  /* `control` is UNCLASSIFIED, not a shade of key — ADR-126 gives
   * it no colour because our data has `key | non_key` and states
   * no automation, so a non-key manual control matches none of the
   * named kinds. A neutral keeps it from reading as a weak key
   * control. */
  --diagram-marker-control: var(--diagram-node-secondary);
  --diagram-font: var(--font-sans);
  --diagram-radius: 6px;
}

/* The workspace shell. A grid rather than floats, so the canvas
 * takes the remaining horizontal space at any Copilot width
 * (§15) and every pane carries `min-width: 0` — without it a long
 * label in the entity list widens its column and pushes the
 * canvas off the right edge. */
.dgw {
  display: grid;
  gap: .5rem;
  font-family: var(--diagram-font);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .75rem;
  background: var(--surface);
}

.dgw-title { margin: 0; font-size: 1rem; }
.dgw-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin: .25rem 0 0;
  font-size: .8125rem;
  color: var(--text-secondary);
}

/* §7: mode is written in the interface. The WORD is in the
 * heading; this only reinforces it. */
.dgw-mode {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 0 .375rem;
  color: var(--text-primary);
}
.dgw[data-mode="edit"] .dgw-mode {
  border-color: var(--diagram-selection);
  background: var(--diagram-selection-tint);
}
.dgw-unsaved { color: var(--warn-ink); font-weight: 600; }

.dgw-bar {
  display: flex;
  flex-wrap: wrap;
  gap: .375rem;
  align-items: center;
  padding-bottom: .375rem;
  border-bottom: 1px solid var(--line);
}
.dgw-group { display: flex; gap: .25rem; }
.dgw-find { flex: 1 1 12rem; min-width: 8rem; }
.dgw-q { width: 100%; }
.dgw-zoom, .dgw-zoomword { color: var(--text-secondary);
  font-variant-numeric: tabular-nums; }

.dgw-main {
  display: grid;
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  gap: .5rem;
  align-items: start;
}
/* A third column only while the shelf is open. 24rem = 384px,
 * inside the 360-420px the review asks for. `:has()` rather than a
 * class the host would have to remember to set and unset: the
 * column exists exactly when the drawer element does, so the two
 * cannot disagree. */
.dgw-main:has(.dgw-drawer) {
  grid-template-columns:
    minmax(0, 18rem) minmax(0, 1fr) minmax(0, 24rem);
}
/* Beside the selection, and stays there while the page scrolls —
 * a shelf you have to scroll away from the diagram to read is the
 * same defect in a different direction. */
.dgw-drawer-host { position: sticky; top: .5rem; }
.dgw-side, .dgw-canvas-wrap, .dgw-drawer { min-width: 0; }

/* The entity list sits in an 18rem column, and a `<table>` takes
 * its MIN-CONTENT width whatever the column says — `min-width: 0`
 * above lets the column shrink, it does not make the table fit.
 * So the table ran 421px past its column and into the canvas,
 * which is a later sibling and paints over it: that is the
 * "visualization is living over the data table" report, and it is
 * an overflow rather than a stacking order.
 *
 * Measured at 1440x900: side column x 261..549, table x 261..978,
 * canvas x 557..1359 — a 421px by 560px intersection. It survives
 * a SUCCESSFUL mount, so it is not a symptom of the canvas
 * failing to start.
 *
 * Scrolled rather than squeezed: `table-layout: fixed` would make
 * every column fit by making the labels illegible, and these are
 * entity names somebody is reading. */
.dgw-list { overflow-x: auto; }

/* §3's canvas sizing: an explicit usable height, minimum 560px.
 * "Embedding the canvas in a content-sized <div> with no height
 * is invalid" — so the height is here and not left to content. */
.dgw-canvas-wrap { position: relative; }
.dgw-canvas {
  min-height: 560px;
  height: 60vh;
  border: 1px solid var(--line);
  border-radius: var(--diagram-radius);
  background: var(--diagram-canvas);
}
.dgw-canvas:focus-visible {
  outline: 2px solid var(--diagram-focus);
  outline-offset: 2px;
}

/* §4: a subtle grid only while editing, and removed in present
 * and export modes. */
.dgw[data-mode="edit"] .dgw-canvas {
  background-image:
    radial-gradient(var(--diagram-grid) 1px, transparent 1px);
  background-size: 16px 16px;
}
.dgw[data-mode="present"] .dgw-canvas { background-image: none; }

/* The anchored popover — the one place in the workspace allowed
 * the overlay shadow (§4: "selection and transient overlays may
 * use the approved overlay shadow"). */
.dgw-pop {
  position: absolute;
  top: .75rem;
  right: .75rem;
  z-index: 5;
  max-width: 22rem;
  padding: .625rem .75rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-overlay);
}
.dgw-pop-head { margin: 0 0 .375rem; }
.dgw-pop-type { color: var(--text-secondary);
  font-size: .75rem; text-transform: uppercase;
  letter-spacing: .04em; }
.dgw-pop-label { font-weight: 650; }

/* PUSH, not overlay, and not a new row.
 *
 * `grid-column: 1 / -1` was inert here: `.dgw-drawer`'s parent is
 * `.dgw-drawer-host`, a plain div, so the property applied to
 * nothing. The HOST is the grid item — and it was the third item
 * in a two-column grid, so it wrapped onto a row of its own BELOW
 * both columns. That is the UI review's "the aside is rendered in
 * document flow more than 1,200px down the page rather than
 * beside the selected item": not a positioning bug, a grid that
 * had no column to put it in.
 *
 * Push rather than overlay because an overlay covers the right
 * edge of the canvas, which is frequently where the element you
 * just clicked is. The cost is a narrower canvas, which is what
 * `panSelectionClear` answers. */
.dgw-drawer {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.dgw-drawer-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: .5rem;
  padding: .5rem .75rem;
  border-bottom: 1px solid var(--line);
}
.dgw-drawer-head h4 { margin: 0; font-size: .9375rem; }
.dgw-drawer-body { padding: .75rem; }

.dgw-status {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  padding-top: .375rem;
  border-top: 1px solid var(--line);
  font-size: .8125rem;
  color: var(--text-secondary);
}
.dgw-invalid, .dgw-trunc { color: var(--warn-ink); }

/* Selection and focus are DIFFERENT signals (§4: "Hover MUST NOT
 * look identical to selection", and keyboard focus gets its own
 * outline). */
.dgw-row.sel > th, .dgw-row.sel > td {
  background: var(--diagram-selection-tint);
}
.dgw-row.focused > th {
  box-shadow: inset 2px 0 0 var(--diagram-focus);
}

.dgw-legend { margin-top: .5rem; }
.dgw-legend h4 { margin: 0 0 .25rem; font-size: .8125rem; }
.dgw-legs { list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .5rem;
  font-size: .75rem; }
.dgw-leg { display: flex; align-items: center; gap: .25rem; }
/* Shape, not colour: §4 forbids colour as the only entity
 * distinction, and a legend is where that promise is kept. */
.dgw-leg-mark {
  width: .625rem;
  height: .625rem;
  border: 1px solid var(--diagram-node-border);
  border-radius: var(--diagram-radius);
  background: var(--diagram-node-subtle);
}
.dgw-leg[data-type="decision"] .dgw-leg-mark {
  border-radius: 0;
  transform: rotate(45deg);
}
.dgw-leg[data-type="start"] .dgw-leg-mark,
.dgw-leg[data-type="end"] .dgw-leg-mark { border-radius: 50%; }
.dgw-leg[data-type="data-store"] .dgw-leg-mark {
  border-radius: 50% / 30%;
}
.dgw-leg[data-type="swimlane"] .dgw-leg-mark,
.dgw-leg[data-type="boundary"] .dgw-leg-mark {
  border-style: dashed;
  background: transparent;
}

.dgw-proposal, .dgw-conflict {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: .75rem;
  margin-top: .5rem;
}
.dgw-diff-part h5 { margin: .5rem 0 .25rem; font-size: .8125rem; }
.dgw-mark { font-family: var(--font-mono); }
.dgw-op { font-weight: 600; }
.dgw-n { color: var(--text-secondary); }

/* §15 tablet/mobile: the entity list and the canvas stack, and
 * the drawer becomes a full-width sheet. Nothing is hidden by a
 * media query — panes the host does not show carry the `hidden`
 * attribute instead, so D-21's stylesheet-off read matches. */
@media (max-width: 900px) {
  .dgw-main,
  .dgw-main:has(.dgw-drawer) {
    grid-template-columns: minmax(0, 1fr);
  }
  .dgw-canvas { min-height: 380px; height: 50vh; }
  /* A full-screen sheet below tablet width: a 384px column on a
   * 380px viewport is not a shelf, it is the whole page with the
   * diagram squeezed behind it. */
  .dgw-drawer-host {
    position: fixed;
    inset: 0;
    z-index: 20;
    overflow: auto;
    padding: .5rem;
    background: var(--surface);
  }
}

/* ==================================================================
   THE DIAGRAM ISLAND'S FUNCTIONAL LAYER — VENDOR CSS, VERBATIM
   ==================================================================

   Source : @xyflow/react 12.11.3 - dist/base.css
   Status : THIRD-PARTY, COPIED WITHOUT MODIFICATION. Do not tidy it
            into house style, do not reformat it, do not "fix" a
            selector in it. It is transforms, positioning, stacking
            and pointer targets; the canvas does not pan, zoom, drag
            or hit-test without it, and the breakage is SILENT -
            things simply stop responding.
   Upgrade: replace this block wholesale from the version pinned in
            package.json. A bug IN here is an upstream report, not a
            local edit - a local edit is lost at the next upgrade and
            nobody will know why the canvas broke.

   WHY IT IS IN styles.css RATHER THAN BESIDE THE ISLAND.
   This is the question the next reader will have, because vendored
   framework CSS looks at a glance like a breach of the
   one-stylesheet rule. It is the opposite: web_development.md says
   all styles live in ONE file, and ADR-066 excepted the FRAMEWORK
   rule for the diagram island - it said nothing about giving the
   island a stylesheet of its own. Putting base.css here PRESERVES
   the one-stylesheet rule; putting it beside the island would be the
   breach. Product owner, 2026-08-19.

   It is base.css, NOT style.css. base.css is the functional minimum
   (13,585 bytes); style.css adds the vendor's own THEME (18,596),
   which is exactly what the alias block after this one replaces.
   Taking the themed file would have put two palettes on the canvas.
   ================================================================== */

/* this will be exported as base.css and can be used for a basic styling */
/* these are the necessary styles for React/Svelte Flow, they get used by base.css and style.css */
.react-flow {
  direction: ltr;

  --xy-edge-stroke-default: #b1b1b7;
  --xy-edge-stroke-width-default: 1;
  --xy-edge-stroke-selected-default: #555;

  --xy-connectionline-stroke-default: #b1b1b7;
  --xy-connectionline-stroke-width-default: 1;

  --xy-attribution-background-color-default: rgba(255, 255, 255, 0.5);

  --xy-minimap-background-color-default: #fff;
  --xy-minimap-mask-background-color-default: rgba(240, 240, 240, 0.6);
  --xy-minimap-mask-stroke-color-default: transparent;
  --xy-minimap-mask-stroke-width-default: 1;
  --xy-minimap-node-background-color-default: #e2e2e2;
  --xy-minimap-node-stroke-color-default: transparent;
  --xy-minimap-node-stroke-width-default: 2;

  --xy-background-color-default: transparent;
  --xy-background-pattern-dots-color-default: #91919a;
  --xy-background-pattern-lines-color-default: #eee;
  --xy-background-pattern-cross-color-default: #e2e2e2;
  background-color: var(--xy-background-color, var(--xy-background-color-default));
  --xy-node-border-default: 1px solid #bbb;
  --xy-node-border-selected-default: 1px solid #555;

  --xy-handle-background-color-default: #333;

  --xy-selection-background-color-default: rgba(150, 150, 180, 0.1);
  --xy-selection-border-default: 1px dotted rgba(155, 155, 155, 0.8);
  --xy-resize-background-color-default: #3367d9;
}
.react-flow.dark {
  --xy-edge-stroke-default: #3e3e3e;
  --xy-edge-stroke-width-default: 1;
  --xy-edge-stroke-selected-default: #727272;

  --xy-connectionline-stroke-default: #b1b1b7;
  --xy-connectionline-stroke-width-default: 1;

  --xy-attribution-background-color-default: rgba(150, 150, 150, 0.25);

  --xy-minimap-background-color-default: #141414;
  --xy-minimap-mask-background-color-default: rgba(60, 60, 60, 0.6);
  --xy-minimap-mask-stroke-color-default: transparent;
  --xy-minimap-mask-stroke-width-default: 1;
  --xy-minimap-node-background-color-default: #2b2b2b;
  --xy-minimap-node-stroke-color-default: transparent;
  --xy-minimap-node-stroke-width-default: 2;

  --xy-background-color-default: #141414;
  --xy-background-pattern-dots-color-default: #555;
  --xy-background-pattern-lines-color-default: #333;
  --xy-background-pattern-cross-color-default: #333;
  --xy-node-color-default: #f8f8f8;
}
.react-flow__background {
  background-color: var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));
  pointer-events: none;
  z-index: -1;
}
.react-flow__container {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}
.react-flow__pane {
  z-index: 1;
  touch-action: none;
}
.react-flow__pane.draggable {
    cursor: grab;
  }
.react-flow__pane.dragging {
    cursor: grabbing;
  }
.react-flow__pane.selection {
    cursor: pointer;
  }
.react-flow__viewport {
  transform-origin: 0 0;
  z-index: 2;
  pointer-events: none;
}
.react-flow__renderer {
  z-index: 4;
}
.react-flow__selection {
  z-index: 6;
}
.react-flow__nodesselection-rect:focus,
.react-flow__nodesselection-rect:focus-visible {
  outline: none;
}
.react-flow__edge-path {
  stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
  stroke-width: var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));
  fill: none;
}
.react-flow__connection-path {
  stroke: var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));
  stroke-width: var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));
  fill: none;
}
.react-flow .react-flow__edges {
  position: absolute;
}
.react-flow .react-flow__edges svg {
    overflow: visible;
    position: absolute;
    pointer-events: none;
  }
.react-flow__edge {
  pointer-events: visibleStroke;
}
.react-flow__edge.selectable {
    cursor: pointer;
  }
.react-flow__edge.animated path {
    stroke-dasharray: 5;
    animation: dashdraw 0.5s linear infinite;
  }
.react-flow__edge.animated path.react-flow__edge-interaction {
    stroke-dasharray: none;
    animation: none;
  }
.react-flow__edge.inactive {
    pointer-events: none;
  }
.react-flow__edge.selected,
  .react-flow__edge:focus,
  .react-flow__edge:focus-visible {
    outline: none;
  }
.react-flow__edge.selected .react-flow__edge-path,
  .react-flow__edge.selectable:focus .react-flow__edge-path,
  .react-flow__edge.selectable:focus-visible .react-flow__edge-path {
    stroke: var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default));
  }
.react-flow__edge-textwrapper {
    pointer-events: all;
  }
.react-flow__edge .react-flow__edge-text {
    pointer-events: none;
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
  }
/* Arrowhead marker styles - use CSS custom properties as default */
.react-flow__arrowhead polyline {
  stroke: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
}
.react-flow__arrowhead polyline.arrowclosed {
  fill: var(--xy-edge-stroke, var(--xy-edge-stroke-default));
}
.react-flow__connection {
  pointer-events: none;
}
.react-flow__connection .animated {
    stroke-dasharray: 5;
    animation: dashdraw 0.5s linear infinite;
  }
svg.react-flow__connectionline {
  z-index: 1001;
  overflow: visible;
  position: absolute;
}
.react-flow__nodes {
  pointer-events: none;
  transform-origin: 0 0;
}
.react-flow__node {
  position: absolute;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  pointer-events: all;
  transform-origin: 0 0;
  box-sizing: border-box;
  cursor: default;
}
.react-flow__node.selectable {
    cursor: pointer;
  }
.react-flow__node.draggable {
    cursor: grab;
    pointer-events: all;
  }
.react-flow__node.draggable.dragging {
      cursor: grabbing;
    }
.react-flow__nodesselection {
  z-index: 3;
  transform-origin: left top;
  pointer-events: none;
}
.react-flow__nodesselection-rect {
    position: absolute;
    pointer-events: all;
    cursor: grab;
  }
.react-flow__handle {
  position: absolute;
  pointer-events: none;
  min-width: 5px;
  min-height: 5px;
  background-color: var(--xy-handle-background-color, var(--xy-handle-background-color-default));
}
.react-flow__handle.connectingfrom {
    pointer-events: all;
  }
.react-flow__handle.connectionindicator {
    pointer-events: all;
    cursor: crosshair;
  }
.react-flow__handle-bottom {
    top: auto;
    left: 50%;
    bottom: 0;
    transform: translate(-50%, 50%);
  }
.react-flow__handle-top {
    top: 0;
    left: 50%;
    transform: translate(-50%, -50%);
  }
.react-flow__handle-left {
    top: 50%;
    left: 0;
    transform: translate(-50%, -50%);
  }
.react-flow__handle-right {
    top: 50%;
    right: 0;
    transform: translate(50%, -50%);
  }
.react-flow__edgeupdater {
  cursor: move;
  pointer-events: all;
}
.react-flow__pane.selection .react-flow__panel {
  pointer-events: none;
}
.react-flow__panel {
  position: absolute;
  z-index: 5;
  margin: 15px;
}
.react-flow__panel.top {
    top: 0;
  }
.react-flow__panel.bottom {
    bottom: 0;
  }
.react-flow__panel.top.center, .react-flow__panel.bottom.center {
      left: 50%;
      transform: translateX(-15px) translateX(-50%);
    }
.react-flow__panel.left {
    left: 0;
  }
.react-flow__panel.right {
    right: 0;
  }
.react-flow__panel.left.center, .react-flow__panel.right.center {
      top: 50%;
      transform: translateY(-15px) translateY(-50%);
    }
.react-flow__attribution {
  font-size: 10px;
  background: var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));
  padding: 2px 3px;
  margin: 0;
}
.react-flow__attribution a {
    text-decoration: none;
    color: #999;
  }
@keyframes dashdraw {
  from {
    stroke-dashoffset: 10;
  }
}
.react-flow__edgelabel-renderer {
  position: absolute;
  width: 100%;
  height: 100%;
  pointer-events: none;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
  left: 0;
  top: 0;
}
.react-flow__viewport-portal {
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  -webkit-user-select: none;
     -moz-user-select: none;
          user-select: none;
}
.react-flow__minimap {
  background: var(
    --xy-minimap-background-color-props,
    var(--xy-minimap-background-color, var(--xy-minimap-background-color-default))
  );
}
.react-flow__minimap-svg {
    display: block;
  }
.react-flow__minimap-mask {
    fill: var(
      --xy-minimap-mask-background-color-props,
      var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default))
    );
    stroke: var(
      --xy-minimap-mask-stroke-color-props,
      var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default))
    );
    stroke-width: var(
      --xy-minimap-mask-stroke-width-props,
      var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default))
    );
  }
.react-flow__minimap-node {
    fill: var(
      --xy-minimap-node-background-color-props,
      var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default))
    );
    stroke: var(
      --xy-minimap-node-stroke-color-props,
      var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default))
    );
    stroke-width: var(
      --xy-minimap-node-stroke-width-props,
      var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default))
    );
  }
.react-flow__background-pattern.dots {
    fill: var(
      --xy-background-pattern-color-props,
      var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default))
    );
  }
.react-flow__background-pattern.lines {
    stroke: var(
      --xy-background-pattern-color-props,
      var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default))
    );
  }
.react-flow__background-pattern.cross {
    stroke: var(
      --xy-background-pattern-color-props,
      var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default))
    );
  }
.react-flow__controls {
  display: flex;
  flex-direction: column;
}
.react-flow__controls.horizontal {
    flex-direction: row;
  }
.react-flow__controls-button {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 26px;
    width: 26px;
    padding: 4px;
  }
.react-flow__controls-button svg {
      width: 100%;
      max-width: 12px;
      max-height: 12px;
      fill: currentColor;
    }
.react-flow__node-input,
.react-flow__node-default,
.react-flow__node-output,
.react-flow__node-group {
  border: var(--xy-node-border, var(--xy-node-border-default));
  color: var(--xy-node-color, var(--xy-node-color-default));
}
.react-flow__node-input.selected,
  .react-flow__node-input:focus,
  .react-flow__node-input:focus-visible,
  .react-flow__node-default.selected,
  .react-flow__node-default:focus,
  .react-flow__node-default:focus-visible,
  .react-flow__node-output.selected,
  .react-flow__node-output:focus,
  .react-flow__node-output:focus-visible,
  .react-flow__node-group.selected,
  .react-flow__node-group:focus,
  .react-flow__node-group:focus-visible {
    outline: none;
    border: var(--xy-node-border-selected, var(--xy-node-border-selected-default));
  }
.react-flow__nodesselection-rect,
.react-flow__selection {
  background: var(--xy-selection-background-color, var(--xy-selection-background-color-default));
  border: var(--xy-selection-border, var(--xy-selection-border-default));
}
.react-flow__resize-control {
  position: absolute;
}
.react-flow__resize-control.left,
.react-flow__resize-control.right {
  cursor: ew-resize;
}
.react-flow__resize-control.top,
.react-flow__resize-control.bottom {
  cursor: ns-resize;
}
.react-flow__resize-control.top.left,
.react-flow__resize-control.bottom.right {
  cursor: nwse-resize;
}
.react-flow__resize-control.bottom.left,
.react-flow__resize-control.top.right {
  cursor: nesw-resize;
}
/* handle styles */
.react-flow__resize-control.handle {
  width: 5px;
  height: 5px;
  border: 1px solid #fff;
  border-radius: 1px;
  background-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
  translate: -50% -50%;
}
.react-flow__resize-control.handle.left {
  left: 0;
  top: 50%;
}
.react-flow__resize-control.handle.right {
  left: 100%;
  top: 50%;
}
.react-flow__resize-control.handle.top {
  left: 50%;
  top: 0;
}
.react-flow__resize-control.handle.bottom {
  left: 50%;
  top: 100%;
}
.react-flow__resize-control.handle.top.left {
  left: 0;
}
.react-flow__resize-control.handle.bottom.left {
  left: 0;
}
.react-flow__resize-control.handle.top.right {
  left: 100%;
}
.react-flow__resize-control.handle.bottom.right {
  left: 100%;
}
/* line styles */
.react-flow__resize-control.line {
  border-color: var(--xy-resize-background-color, var(--xy-resize-background-color-default));
  border-width: 0;
  border-style: solid;
}
.react-flow__resize-control.line.left,
.react-flow__resize-control.line.right {
  width: 1px;
  transform: translate(-50%, 0);
  top: 0;
  height: 100%;
}
.react-flow__resize-control.line.left {
  left: 0;
  border-left-width: 1px;
}
.react-flow__resize-control.line.right {
  left: 100%;
  border-right-width: 1px;
}
.react-flow__resize-control.line.top,
.react-flow__resize-control.line.bottom {
  height: 1px;
  transform: translate(0, -50%);
  left: 0;
  width: 100%;
}
.react-flow__resize-control.line.top {
  top: 0;
  border-top-width: 1px;
}
.react-flow__resize-control.line.bottom {
  border-bottom-width: 1px;
  top: 100%;
}


/* ==================================================================
   ...AND ITS APPEARANCE, MAPPED ONTO SECTION 4's TOKENS
   ==================================================================

   base.css consumes every colour as
   var(--xy-thing, var(--xy-thing-default)), so setting the
   NON-default half is the sanctioned override point: the vendor's
   defaults stay untouched as the fallback, and ours win.

   This is the half that matters. Without it React Flow renders in
   its own greys - #b1b1b7 edges, #bbb node borders - BESIDE our
   tokens rather than under them. That is a working canvas which
   drifts from the Cytoscape one, and "users cannot tell which
   renderer is underneath" is the whole property the adapter seam
   exists to hold.

   Every value is a var(--diagram-*) reference. No hex here: a
   literal would be a third copy of the palette, after the one
   diagrams/DESIGN.md section 4 already records drifting twice.

   Scoped to .react-flow rather than :root deliberately - these names
   belong to one vendor in one island, and hoisting them to the
   document root would imply the rest of the app knows what an --xy-
   is.

   The *-width variables are NOT mapped. They are geometry, not
   colour, and section 4 carries no stroke-width token to inherit
   from - the same reason --diagram-radius is the one literal
   permitted there.
   ================================================================== */

.react-flow {
  /* Edges, and the connection line being drawn. */
  --xy-edge-stroke: var(--diagram-edge);
  --xy-edge-stroke-selected: var(--diagram-selection);
  --xy-connectionline-stroke: var(--diagram-edge-emphasis);

  /* Nodes. The border is a shorthand, so the token sits inside it. */
  --xy-node-color: var(--diagram-node-text);
  --xy-node-border: 1px solid var(--diagram-node-border);
  --xy-node-border-selected: 1px solid var(--diagram-selection);

  /* Port handles. Section 6's port-aware connection rules are what a
     person aims at, so these have to be visible against the node. */
  --xy-handle-background-color: var(--diagram-edge);

  /* Canvas. Section 4: a subtle grid only while editing, reduced or
     absent in present and export modes. */
  --xy-background-color: var(--diagram-canvas);

  /* Marquee selection and the resize control. */
  --xy-selection-background-color: var(--diagram-selection-tint);
  --xy-selection-border: 1px dotted var(--diagram-selection);
  --xy-resize-background-color: var(--diagram-selection);

  /* Minimap. Section 9 makes it a toggle, so it must be themed for
     when it is ON rather than assumed off. */
  --xy-minimap-background-color: var(--diagram-node-surface);
  --xy-minimap-node-background-color: var(--diagram-node-subtle);
  --xy-minimap-node-stroke-color: var(--diagram-node-border);
  --xy-minimap-mask-background-color: var(--diagram-canvas);
  --xy-minimap-mask-stroke-color: var(--diagram-grid-strong);

  /* The library's own attribution chip. */
  --xy-attribution-background-color: var(--diagram-node-surface);
}

/* The grid pattern reads ONE variable for all three patterns:
   var(--xy-background-pattern-color-props,
       var(--xy-background-pattern-color,
           var(--xy-background-pattern-<kind>-color-default))).
   The -props layer is what React passes per instance and is not
   ours; the -default layer is the vendor's fallback. So the
   stylesheet's override point is the MIDDLE one, and setting it on
   each pattern element is what lets section 4's two grid weights
   survive a single vendor variable.

   Written this way after a check caught the obvious version being
   wrong: --xy-background-pattern-dots-color reads plausibly and is
   consumed by nothing, so the three declarations would have been
   silently dead. */
.react-flow__background-pattern.dots,
.react-flow__background-pattern.lines {
  --xy-background-pattern-color: var(--diagram-grid);
}

.react-flow__background-pattern.cross {
  --xy-background-pattern-color: var(--diagram-grid-strong);
}

/* ==================================================================
   THE DIAGRAM COMPONENT RULES — written by the React Flow island
   team, placed verbatim
   ==================================================================

   The token layer above decides colours; this decides shape. Both
   were needed and only the first had an owner: the component rules
   fell in the seam between "the host owns styling" and "the renderer
   owns the canvas", which neither mandate named. Before this block
   the tokens existed and NOTHING consumed them — 22 aliases, 70
   --xy- mappings, and zero .diagram-node rules — so the SVG export
   rendered to section 4 while the live canvas was unstyled boxes
   with overflowing text. A beautiful export of a diagram that looked
   broken on screen.

   Verified before placing rather than taken on report: ZERO hex
   literals, 41 var(--diagram-*) uses, and every one of the 20 tokens
   it references is declared above — so design_tokens_test.py remains
   the single place a colour is decided.

   MIRRORED IN THE SVG EXPORT, so two renderings of one diagram
   cannot drift: shape per family, type marker, label size and
   weight, worded state, the AI-proposed marker, container boundary
   and label, and an event label sitting outside its circle.

   CANVAS-ONLY AND DELIBERATELY ABSENT FROM THE EXPORT: hover, focus
   ring, selection, ports, grid, transitions. Do not "fix" the export
   to match — section 14 requires their absence.

   OVERFLOW: labels wrap and never truncate, and the export agrees on
   purpose. Section 4 permits truncation only when the full label is
   reachable on focus and in a detail surface, and a static export
   offers neither.

   TWO RULES HERE ARE THE PRICE OF TAKING base.css OVER style.css,
   and that trade was made deliberately one commit earlier to avoid
   two palettes on one canvas. base.css styles
   .react-flow__edge-textwrapper and .react-flow__edge-text for
   pointer-events ONLY and supplies no fill, so both fall to SVG's
   initial black — every edge label rendered as a solid black box
   with black text. No --xy- variable exists for it, so no mapping
   could have covered it. Found by looking at the screen; nobody
   would find it except on a diagram with edge labels, and both
   shipped examples label every edge. A RENDERER DRAWING EDGE LABELS
   INHERITS THIS.
   ================================================================== */

/* ── Diagram island: component rules ─────────────────────────────
 *
 * The classes src/web/diagrams/react_flow/primitives.jsx emits.
 * The seam landed §4's `--diagram-*` tokens, React Flow's
 * base.css and the `--xy-*` mappings — the substrate — and
 * nothing consumes them yet, so the canvas renders as unstyled
 * boxes with overflowing text while the SVG export is correct.
 *
 * EVERY VALUE IS A `var(--diagram-*)`. No hex literal anywhere, so
 * design_tokens_test.py stays the one place a colour is decided.
 * A token that does not exist should be ADDED to §4's block rather
 * than worked around here — the island's export module holds the
 * same line and degrades to `currentColor` rather than inventing.
 *
 * MIRRORED IN THE SVG EXPORT (must not drift): shape per entity
 * family, the type marker above the label, the label's size and
 * weight, the worded state below it, the AI-proposed marker, the
 * container's dashed boundary and its label, and a start/end
 * event's label sitting OUTSIDE its circle.
 *
 * CANVAS-ONLY, deliberately absent from the export (§14 requires
 * their absence, so do not "fix" the export to match): hover,
 * focus ring, selection, ports/handles, the grid, and any
 * transition.
 *
 * OVERFLOW: long labels WRAP. They are never truncated, because
 * §4 permits truncation only when the full label is available on
 * focus and in the detail surface — and the export cannot offer
 * either, so it wraps too. The two answers agree on purpose.
 */

/* ── the node ─────────────────────────────────────────────────── */

.diagram-node {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  box-sizing: border-box;
  min-width: 140px;
  max-width: 260px;
  padding: 6px 10px;
  font-family: var(--diagram-font);
  color: var(--diagram-node-text);
  background: var(--diagram-node-surface);
  border: 1px solid var(--diagram-node-border);
  border-radius: var(--diagram-radius);
  /* §4: "Keep ordinary diagram objects flat" — no shadow, no
   * gradient, no glow. */
  text-align: center;
  /* Wrapped, never clipped: see OVERFLOW above. */
  overflow-wrap: anywhere;
}

/* §4's node hierarchy, in its stated order: type marker, primary
 * label, identifier, one status line, provenance marker. */
.diagram-node-type {
  font-size: 10px;
  line-height: 1.2;
  letter-spacing: 0.02em;
  color: var(--diagram-node-secondary);
}

.diagram-node-label {
  /* §4: "13–14 px, weight 650–700". */
  font-size: 13px;
  font-weight: 650;
  line-height: 1.25;
}

.diagram-node-id,
.diagram-node-state {
  /* §4: "Node metadata: 11–12 px, regular or medium". */
  font-size: 11px;
  color: var(--diagram-node-secondary);
}

.diagram-node-proposed {
  font-size: 10px;
  font-weight: 600;
  color: var(--diagram-warning);
}

/* ── §4's shape table: shape carries the entity FAMILY ────────── */

/* #85: a CLIPPED shape declares its polygon as `--shape` and is drawn
 * by the two layers further down, not by `clip-path` on itself —
 * custom properties inherit into pseudo-elements, so one declaration
 * feeds both. Nothing here sets `clip-path`; see the layer block. */

/* Activity, process step, control: rounded rectangle (the base). */

/* Decision: a diamond. Extra horizontal padding because the shape
 * tapers away from the text on both sides. */
.diagram-node-decision {
  --shape: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
  padding: 10px 26px;
  min-width: 180px;
  border-radius: 0;
}

/* Start/end event: a circle, with the label BESIDE it — §4, and
 * the arrangement the export uses. The circle is its own glyph so
 * the text can sit outside it. */
.diagram-node-event {
  background: none;
  border: none;
  padding: 0;
  min-width: 0;
  gap: 4px;
}
.diagram-node-event .diagram-node-glyph {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--diagram-node-surface);
  border: 1px solid var(--diagram-node-border);
}
.diagram-node-event .diagram-node-type {
  /* The word "start"/"end" is carried by the shape and the label;
   * repeating it under the circle is noise. Still in the
   * accessible name on the wrapper. */
  display: none;
}

/* ADR-125's boundary terminals: a CHEVRON, mirrored — §4's row,
 * "pointing inward for a source and outward for a sink". The
 * mirroring is the meaning, not decoration: a terminal says where
 * data crosses the boundary and unavoidably which way, so
 * direction has to survive greyscale and fit scale. Deliberately
 * NOT a circle and not a half-round: the first is the business
 * process's start/end event, which ADR-125 forbids these being
 * mapped onto, and the second reads as that circle when scaled
 * down — the same failure one step quieter.
 *
 * clip-path rather than a border trick, following
 * `.diagram-node-decision` above: the notch has to be a real cut
 * in the box or the border draws straight through it. Padding
 * compensates for the points, as the diamond's does. */
.diagram-node-source,
.diagram-node-sink {
  border-radius: 0;
  /* The padding is what keeps a label clear of the notch, and it
   * is NOT numerically the same allowance the export makes:
   * 170 - 60 = 110px here against the export's
   * 180 * 0.56 - 16 = 84.8px. Both clear the notch at 22%, so
   * neither overflows, but "canvas and paper agree" would be
   * false — what is true is that neither overflows. Deriving both
   * from `CHEVRON` would make them agree; CSS cannot read that
   * table, so this note is the honest alternative. */
  padding: 8px 30px;
  min-width: 170px;
}
/* Source: notch on the boundary side, apex facing the diagram. */
.diagram-node-source {
  --shape: polygon(
    0% 0%, 72% 0%, 100% 50%, 72% 100%, 0% 100%, 22% 50%);
}
/* Sink: the same shape mirrored. Its notch faces the right
 * boundary and its apex faces the diagram, exactly as the
 * source's do on the left — §4 requires the PAIR to be mirror
 * images, and "outward for a sink" was the wording it had
 * before that turned out to describe two identical shapes. */
.diagram-node-sink {
  --shape: polygon(
    28% 0%, 100% 0%, 78% 50%, 100% 100%, 28% 100%, 0% 50%);
}

/* Data store: the database/cylinder convention. */
.diagram-node-store {
  border-radius: 50% / 14px;
  padding: 14px 10px;
}

/* System or application, and external party: a rectangle with a
 * marker. The marker is a border treatment rather than an icon,
 * because §4 forbids colour as the only distinction and a shape
 * cue survives monochrome printing. */
.diagram-node-system {
  border-radius: 2px;
}
.diagram-node-external {
  border-radius: 2px;
  border-style: dashed;
}

/* Risk or issue: "rounded rectangle with a semantic leading
 * marker" (§4). */
.diagram-node-risk {
  border-left: 4px solid var(--diagram-danger);
}

/* Evidence or document. */
.diagram-node-document {
  border-radius: var(--diagram-radius) var(--diagram-radius)
    var(--diagram-radius) 0;
}

/* Annotation: a note, not an entity. */
.diagram-node-annotation {
  background: var(--diagram-node-subtle);
  border-style: dashed;
  font-style: italic;
}

/* ── operational state: never colour ALONE (§4) ───────────────── */
/* Each of these pairs a tint with a border treatment, and the
 * state is ALSO written as a word by `.diagram-node-state`. */

.diagram-node.is-warning {
  background: var(--diagram-warning-tint);
  border-color: var(--diagram-warning);
}
.diagram-node.is-exception,
.diagram-node.is-blocked {
  background: var(--diagram-danger-tint);
  border-color: var(--diagram-danger);
}
.diagram-node.is-complete {
  background: var(--diagram-success-tint);
  border-color: var(--diagram-success);
}
.diagram-node.is-archived {
  color: var(--diagram-node-secondary);
  border-style: dotted;
}

/* Importance, which is not a state: a heavier edge, not a colour. */
.diagram-node.is-key { border-width: 2px; }
.diagram-node.is-material {
  border-width: 2px;
  box-shadow: inset 0 0 0 1px var(--diagram-node-border);
}

/* An AI-proposed element is visibly a proposal (§12, §14). */
.diagram-node.is-proposed {
  border-style: dashed;
  border-color: var(--diagram-warning);
}

/* #93, and the same shape as `is-proposed` directly below: the
 * COLOUR states painted the wrapper, not the circle. §4's first
 * sentence is "shape communicates entity family, not lifecycle
 * state", and `.diagram-node-event { background: none }` is ONE
 * class while `.diagram-node.is-complete` is TWO — so the tint
 * won and a completed event drew as a tinted rounded rectangle
 * with the circle inside it.
 *
 * This is #85's defect, fixed there for the decision and the two
 * terminals and never extended to the event. It stayed invisible
 * because every committed business-process example uses
 * `is-not-started`, which carries no tint: the whole fixture
 * corpus was blind to it, and a screenshot found it while every
 * computed-style assertion about the circle passed.
 *
 * THE KNOWN GAP RECORDED ON THE CLIPPED PRIMITIVES DOES NOT
 * APPLY HERE, and the first version of this comment said it did.
 * On a clipped primitive `is-archived` and `is-material` are
 * LOST — a filled layer cannot be dashed and the border is
 * clipped away. On an event they did the OPPOSITE: they painted,
 * a 3px dotted rectangle and a 1px inset ring around the circle
 * and its label. Measured in Chromium, which is the only reason
 * it was found; a recorded gap describing the reverse of what
 * happens is worse than no note, because the next reader plans
 * against it. Both are killed on the base rule above. `is-key`
 * is genuinely inert (0px). `is-proposed` is re-routed */
/* The reset that has to WIN, and specificity is only half of why
 * it is written this way. `.diagram-node.is-material` is TWO
 * classes and sets `border-width: 2px` and an inset shadow;
 * `.diagram-node.is-archived` is two and sets `border-style:
 * dotted`, which revives the base shorthand's `medium`. A
 * one-class `.diagram-node-event { border: none }` loses to both,
 * so an archived+material event drew a 3px dotted rectangle with
 * a 1px inset ring around the circle AND its label, the box
 * growing 131px -> 151px. Two classes ties them, so this must
 * also sit BELOW them in source order — it does, and moving it
 * above would silently restore the bug.
 *
 * This is #85's fix, which `css_rules.test.js` already asserts
 * for the decision and the two terminals. The event kind was
 * never added to that list; #93 is that omission. */
.diagram-node.diagram-node-event {
  border-width: 0;
  box-shadow: none;
}

.diagram-node.diagram-node-event.is-warning,
.diagram-node.diagram-node-event.is-exception,
.diagram-node.diagram-node-event.is-blocked,
.diagram-node.diagram-node-event.is-complete {
  background: none;
  border: none;
}
.diagram-node.diagram-node-event.is-warning .diagram-node-glyph {
  background: var(--diagram-warning-tint);
  border-color: var(--diagram-warning);
}
.diagram-node.diagram-node-event.is-exception .diagram-node-glyph,
.diagram-node.diagram-node-event.is-blocked .diagram-node-glyph {
  background: var(--diagram-danger-tint);
  border-color: var(--diagram-danger);
}
.diagram-node.diagram-node-event.is-complete .diagram-node-glyph {
  background: var(--diagram-success-tint);
  border-color: var(--diagram-success);
}

/* On an event the treatment belongs to the CIRCLE, not to the stack
 * that holds the circle and its outside label. Without this the
 * dashed border boxed the label and the state as well, which reads
 * as a proposed GROUP rather than a proposed event. `border: none`
 * is restated because a later `border-style` alone revives the
 * shorthand's `medium` width — the border reappeared at 3px from a
 * rule that only meant to change its style. */
/* THREE classes, matching the colour-state rules above, so
 * SOURCE ORDER decides between them and not specificity. Written
 * with two first: hardening the tints to three made them
 * out-specify this, and a proposed+complete glyph edge went amber
 * -> green again — the same regression, reintroduced by the fix
 * for a different one, and caught by the browser rather than by
 * the ordering test, which asks about order and cannot see
 * specificity. Keep these two families at equal weight. */
.diagram-node.diagram-node-event.is-proposed {
  border: none;
}
.diagram-node.diagram-node-event.is-proposed
.diagram-node-glyph {
  border-style: dashed;
  border-color: var(--diagram-warning);
}



/* ── canvas-only: interaction. NOT in the export ──────────────── */

/* §4: "Hover MUST NOT look identical to selection." Hover moves
 * the border only; selection adds a tint AND a 2px brand border. */
.diagram-node:hover {
  border-color: var(--diagram-edge-emphasis);
}

.react-flow__node.selected .diagram-node {
  border: 2px solid var(--diagram-selection);
  background: var(--diagram-selection-tint);
}

/* §4: "Keyboard focus uses a SEPARATE visible outline" — distinct
 * from selection, and it must survive the drawer opening. */
.react-flow__node:focus-visible .diagram-node,
.diagram-node:focus-visible {
  outline: 2px solid var(--diagram-focus);
  outline-offset: 2px;
}

/* A container is NOT a `.diagram-node` — `primitives.jsx`'s
 * `Container` renders `.diagram-container` — so the suppression
 * below removed its focus ring with nothing replacing it. It is a
 * real focus stop and often the first one a keyboard user lands on.
 * §16 wants a visible ring on every focusable, and review measured
 * this one losing it outright. */
.react-flow__node:focus-visible .diagram-container {
  outline: 2px solid var(--diagram-focus);
  outline-offset: 2px;
}

/* ONE ring, not two. The site-wide `:focus-visible` rule also paints
 * on React Flow's wrapper, and the wrapper is sized from the
 * document's declared node height while the glyph auto-sizes to its
 * content — 180x80 against 180x64 for a terminal. Both rings paint,
 * so focus read as two overlapping rectangles with a strip below the
 * shape: an artifact, not an indicator. Found by looking at a
 * screenshot; every computed-style assertion passed.
 *
 * Suppressed HERE and not at the global rule, which every other
 * focusable in the app depends on. Safe only because the rule above
 * now paints — until #85 the glyph's outline was swallowed by
 * `clip-path` and the wrapper's ring was the only one, which is why
 * removing it then would have left focus invisible. */
.react-flow__node:focus-visible {
  outline: none;
}

/* ── #85: a clipped primitive gets its edge back ──────────────── */

/* `clip-path` clips the BORDER BOX, so everything painted on the
 * edge disappears with it. The diamond had no border at all — all
 * four of its edges are diagonal — and the chevron kept one only on
 * its two flat edges, while the SVG export stroked all six. That is
 * the very property §4 names as the discriminator between a terminal
 * and an arrowhead. Measured in Chromium 2026-08-27; the diamond
 * predates the chevron, so this was never a chevron bug.
 *
 * The shape moves to two layers that inherit it through `--shape`,
 * and THE PARENT STOPS BEING CLIPPED. Three of the four symptoms
 * are then fixed by removal rather than by new CSS: the focus
 * outline above starts painting, and the sink's outbound handle
 * becomes hit-testable — it sat inside the clipped-away notch, so
 * `elementFromPoint` returned the pane and a user could not begin
 * the drag `SINK_HAS_NO_EXIT` exists to refuse.
 *
 * `isolation: isolate` keeps the negative z-indexes inside the node
 * rather than dropping them behind the canvas. */
/* `.diagram-node` is repeated in each selector to reach TWO
 * classes, and that is not decoration. The state rules above are
 * `.diagram-node.is-complete` — two classes — so a one-class
 * `.diagram-node-source { background: transparent }` LOSES to them,
 * and the state tint went on painting as a RECTANGLE once the parent
 * stopped being clipped. It showed as green triangles filling the
 * chevron's notch and apex, and every computed-style assertion
 * passed while it did: the layers were correct, and the thing in
 * front of them was not. A screenshot found it. */
.diagram-node.diagram-node-decision,
.diagram-node.diagram-node-source,
.diagram-node.diagram-node-sink {
  border: 0;
  background: transparent;
  /* `is-material` sets `box-shadow: inset 0 0 0 1px`, which paints on
   * the BORDER BOX — clipped away before, and a grey rectangle round
   * the shape after. Resetting `border` and `background` was not
   * enough; review measured a material diamond reading as a
   * rectangle with a diamond inside it. */
  box-shadow: none;
  position: relative;
  isolation: isolate;
  --shape-stroke: var(--diagram-node-border);
  --shape-fill: var(--diagram-node-surface);
  --shape-stroke-hover: var(--diagram-edge-emphasis);
}

/* The state tints, re-routed onto the layers. The parent's own
 * `background`/`border-color` cannot be used — CSS has no way to
 * read a computed value into a custom property — so each state that
 * carries a COLOUR is mapped here.
 *
 * KNOWN GAP, recorded rather than hidden: `is-archived` (dotted),
 * `is-proposed` (dashed) and `is-key`/`is-material` (2px, inset
 * shadow) express themselves through border STYLE and WIDTH, and a
 * filled layer cannot be dashed. On a clipped primitive those
 * treatments are lost. They were ALREADY lost on the diamond, whose
 * four diagonal edges clipped every border away, and partly lost on
 * the chevron — so this is not a regression, but §12/§14 want a
 * proposal to be visibly a proposal and a 1px solid edge does not
 * say so. Tracked on #85. */
.diagram-node.diagram-node-decision.is-warning,
.diagram-node.diagram-node-source.is-warning,
.diagram-node.diagram-node-sink.is-warning {
  --shape-fill: var(--diagram-warning-tint);
  --shape-stroke: var(--diagram-warning);
}
.diagram-node.diagram-node-decision.is-exception,
.diagram-node.diagram-node-source.is-exception,
.diagram-node.diagram-node-sink.is-exception,
.diagram-node.diagram-node-decision.is-blocked,
.diagram-node.diagram-node-source.is-blocked,
.diagram-node.diagram-node-sink.is-blocked {
  --shape-fill: var(--diagram-danger-tint);
  --shape-stroke: var(--diagram-danger);
}
.diagram-node.diagram-node-decision.is-complete,
.diagram-node.diagram-node-source.is-complete,
.diagram-node.diagram-node-sink.is-complete {
  --shape-fill: var(--diagram-success-tint);
  --shape-stroke: var(--diagram-success);
}

/* `is-proposed` LAST, so it outranks the operational state — which
 * is what an unclipped node already does and what this block had
 * missed. Measured 2026-08-29: `.diagram-node-system.is-complete
 * .is-proposed` computes `dashed rgb(150,101,19)`, amber, because
 * `.diagram-node.is-proposed` sits after the state rules in source
 * order. The clipped primitives took only the four OPERATIONAL
 * states onto `--shape-stroke` and left `is-proposed` behind, so
 * the same pair computed the success green: a proposed terminal
 * read as complete and nothing said it was a proposal.
 *
 * That is a REGRESSION this change introduced, not one it
 * inherited. Before #85 a chevron's two flat edges survived the
 * clip and carried `1px dashed rgb(150,101,19)`; afterwards they
 * carried 1px solid green. Found by an independent verifier
 * against the pre-#85 stylesheet restored into the live CSSOM,
 * and recorded in neither the issue, the plan, DESIGN.md nor
 * diagrams.md before that.
 *
 * The COLOUR is restored here. The DASH is not, and cannot be: a
 * filled layer has no stroke style to dash. Section 12/14 want a
 * proposal to be visibly a proposal, and amber-versus-state is a
 * weaker signal than amber-and-dashed — the remaining half is the
 * mechanism's limit, tracked on #85. */
.diagram-node.diagram-node-decision.is-proposed,
.diagram-node.diagram-node-source.is-proposed,
.diagram-node.diagram-node-sink.is-proposed {
  --shape-stroke: var(--diagram-warning);
}

/* THE LAYER SELECTORS NAME NO KIND, and that is not stylistic.
 * `clipPathFor` reports every rule that mentions a kind AND sets a
 * shape as an `overrides` entry, and the drift tests REFUSE a
 * non-empty list — correctly, since a higher-specificity
 * `clip-path: none` would erase the shape the export assumes. A
 * kind-named layer rule trips that guard, so the layers are gated on
 * the PROPERTIES instead: a node that declares no `--shape-stroke`
 * gets two transparent, unclipped, non-painting layers.
 *
 * `var(--shape)` with `--shape` unset is invalid at computed-value
 * time, so `clip-path` falls back to `none` — which is why the
 * background gate, not the clip, is what decides whether a layer is
 * visible. */
.diagram-node::before,
.diagram-node::after {
  content: "";
  position: absolute;
  clip-path: var(--shape);
  /* The layers are decoration; hit-testing belongs to the node. */
  pointer-events: none;
}

/* The stroke layer. */
.diagram-node::before {
  inset: 0;
  z-index: -2;
  background: var(--shape-stroke, transparent);
}

/* The fill layer, 1px smaller — the DIFFERENCE is the stroke, which
 * is why this needs no border of its own and cannot be clipped away
 * from one side. */
.diagram-node::after {
  inset: 1px;
  z-index: -1;
  background: var(--shape-fill, transparent);
}

.diagram-node:hover::before {
  background: var(--shape-stroke-hover, var(--shape-stroke,
    transparent));
}

/* §4: selection is a tint AND a 2px brand border. It paints on the
 * LAYERS rather than on the node's border, for a reason worth
 * stating: `border: 2px` would shrink the padding box, and the
 * layers are inset from it, so the glyph would visibly shrink on
 * selection. Widening the stroke layer instead keeps the box still
 * and gives an edge that follows the shape. */
.react-flow__node.selected .diagram-node.diagram-node-decision,
.react-flow__node.selected .diagram-node.diagram-node-source,
.react-flow__node.selected .diagram-node.diagram-node-sink {
  border: 0;
  background: transparent;
}
.react-flow__node.selected .diagram-node.diagram-node-decision,
.react-flow__node.selected .diagram-node.diagram-node-source,
.react-flow__node.selected .diagram-node.diagram-node-sink {
  --shape-stroke: var(--diagram-selection);
  --shape-fill: var(--diagram-selection-tint);
  --shape-stroke-hover: var(--diagram-selection);
}
/* 2px, per §4 — widened by insetting the fill, so the node's box
 * never moves and the glyph does not jump on selection. */
.react-flow__node.selected .diagram-node::after {
  inset: 2px;
}

/* Ports: edge anchors first, connection affordances second. Sized
 * for a pointer without dominating the node. */
.diagram-port {
  width: 8px;
  height: 8px;
  background: var(--diagram-node-surface);
  border: 1px solid var(--diagram-edge);
  border-radius: 50%;
}
.diagram-port:hover {
  background: var(--diagram-edge-emphasis);
  border-color: var(--diagram-edge-emphasis);
}

/* ── containers: swimlane, group, boundary ────────────────────── */

.diagram-container {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  /* §4: "a low-emphasis container" — a dashed boundary and a tinted
   * ground, never a filled panel that competes with its contents. */
  background: var(--diagram-node-subtle);
  border: 1px dashed var(--diagram-grid-strong);
  border-radius: var(--diagram-radius);
}

.diagram-container-label {
  position: absolute;
  top: 6px;
  left: 10px;
  /* §4: "Group or swimlane label: 12 px, weight 700, sentence
   * case." Sentence case is the author's; this does not
   * text-transform it. */
  font-family: var(--diagram-font);
  font-size: 12px;
  font-weight: 700;
  color: var(--diagram-node-secondary);
}

/* ── edge labels: REQUIRED, and base.css does not fill them ───── */
/*
 * Found by rendering a diagram and looking at it: every edge label
 * drew as a SOLID BLACK BOX with black text on it.
 *
 * Not a mapping gap — `--xy-edge-label-*` does not exist. base.css
 * styles `.react-flow__edge-textwrapper` and `.react-flow__edge-text`
 * for `pointer-events` ONLY and provides no `fill`, so both default
 * to SVG's initial black. React Flow's `style.css` — the vendor
 * theme we deliberately did NOT take, because it would have put two
 * palettes on one canvas — is what normally fills them. Taking
 * base.css was right; these two rules are its consequence.
 *
 * Nobody would find this except on a diagram that has edge labels.
 * Both shipped examples label every edge.
 *
 * §4 asks for "a surface-colored halo or label plate", which is the
 * same decision the SVG export reached independently — so this
 * MIRRORS the export rather than diverging from it.
 */
.react-flow__edge-textbg {
  fill: var(--diagram-canvas);
}

.react-flow__edge-text {
  fill: var(--diagram-node-text);
  font-family: var(--diagram-font);
  /* §4: "Edge label: 11–12 px". */
  font-size: 11px;
}

/* ── the grid: editing only (§4) ──────────────────────────────── */
/* base.css already drives the pattern from `--xy-background-*`,
 * which the seam maps to `--diagram-grid`. Nothing needed here;
 * the island renders <Background> only in edit mode. */

/* ══ the Process Workbench page (views/workbench.js) ══════════ */
/* Layout only. The workspace inside `.wb-panel` styles itself
 * (`.dgw*` above) and the family switch reuses `.facets`, so what
 * is left here is the selector, the chosen-process line and the
 * graph's two arrangement actions. */

.wb-find {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0 0 12px;
}
.wb-q { min-width: 16rem; max-width: 100%; }

/* ADR-124 wants the three family buttons reading as part of the
 * toolbar band, in the same position on every family. They cannot
 * live INSIDE `.dgw-bar`: the workspace repaints that element from
 * its snapshot on every change, so anything the page injected
 * would be wiped on the first selection. The family switch is also
 * genuinely a page control — it chooses WHICH document the
 * workspace shows, while the workspace owns one document at a
 * time.
 *
 * So the two stay separate elements, and what is actually
 * guaranteed is POSITION STABILITY rather than adjacency: §3's
 * page heading legitimately sits between them, so the strip is
 * not flush with the toolbar and 70px separates the two. What
 * holds is that every control lands in the same place on all
 * three families — facets at x=474, the toolbar at y=275,
 * measured on each. That is the property ADR-124 actually needs;
 * "one continuous band" was the first description of it and was
 * wrong about the heading. */
.wb-top {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 16px;
  margin: 0;
}
/* NOT `margin-left: auto`. Right-aligning made the strip's x
 * depend on its own label widths, so the three family buttons
 * landed at 1016, 1022 and 1015 across the three families —
 * measured. ADR-124 asks for the same POSITION on every family,
 * and a right-aligned control cannot give that while the labels
 * differ. Left, in order, after the chosen line. */
.wb-top .facets { order: 1; }
.wb-top .wb-layouts { order: 2; margin: 0; }
.wb-chosen {
  margin: 0;
  color: var(--text-secondary);
  font-size: 13px;
}
.wb-chosen b { color: var(--text-primary); }

/* §7's automatic layout, as two explicit actions. */
/* On the band's own row, not a row of its own. These actions
 * exist for the knowledge graph only, and while they occupied a
 * separate line their PRESENCE moved everything below: the
 * toolbar sat at y=287 on two families and y=317 on the graph,
 * measured, which is exactly the re-scan ADR-124 exists to
 * prevent. The arrangement actions may differ per family; the
 * position of every other control may not. */
.wb-layouts {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0;
}
.wb-lay-label { color: var(--text-secondary); font-size: 12px; }

/* The pre-layout estimate. Deliberately NOT warn-coloured: it
 * reports a wait that is about to be spent, which is information
 * rather than a fault, and colouring it as a warning would make
 * the one real warning on the page harder to find. */
.wb-warn {
  flex-basis: 100%;
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0;
  color: var(--text-secondary);
  font-size: 12px;
}

/* ══ ADR-124: view controls overlay the canvas ═════════════════ */

/* The cluster sits on the thing it acts on. `.dgw-canvas-wrap` is
 * already `position: relative` for the anchored popover, so this
 * needs no new positioning context — but it does now SHARE one
 * with that popover, and §8 requires the popover track its element
 * through pan and zoom. The popover is given the higher layer
 * because it is tied to a specific element the user just clicked,
 * while these controls are always in the same corner and can be
 * reached around. */
.dgw-viewctl {
  position: absolute;
  top: .5rem;
  right: .5rem;
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: .25rem;
  max-width: calc(100% - 1rem);
  padding: .25rem;
  border: 1px solid var(--line);
  border-radius: var(--diagram-radius);
  /* Opaque, not translucent: these sit over diagram content, and a
   * semi-transparent panel makes both the control and the node
   * behind it harder to read than either alone. */
  background: var(--surface);
}
.dgw-pop { z-index: 3; }

/* A labeled menu — `<details>`, so open/closed needs no script and
 * survives nothing, because a re-render replaces the element. That
 * is the one cost: a menu closes when the snapshot repaints. It is
 * accepted deliberately, since every entry in these two menus
 * either changes the mode or starts an export, and both repaint. */
.dgw-menu { position: relative; display: inline-block; }
.dgw-menu > summary {
  cursor: pointer;
  list-style: none;
}
.dgw-menu > summary::-webkit-details-marker { display: none; }
.dgw-menu > summary::after { content: " \25be"; }
.dgw-menu-list {
  position: absolute;
  z-index: 4;
  top: calc(100% + .25rem);
  left: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .125rem;
  min-width: 12rem;
  padding: .25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-overlay, 0 4px 12px rgba(0, 0, 0, .18));
}
.dgw-menu-list .btn { justify-content: flex-start; text-align: left; }

/* ══ §3's optional column, as a Diagram | List switch ══════════ */

/* §3 requires the entity list to EXIST — "synchronized text
 * representation, searchable and keyboard operable" — and labels
 * the column it lived in "Optional palette or entity list", twice.
 * So one pane at a time is inside the section as written: the list
 * stays reachable and stops holding a third of the width.
 *
 * Measured before: with the list and the detail shelf both open,
 * the canvas held 410px of 1082 — 38% of the workspace, for the
 * diagram the workspace is for. */
.dgw[data-pane="diagram"] .dgw-side { display: none; }
.dgw[data-pane="diagram"] .dgw-main {
  grid-template-columns: minmax(0, 1fr);
}
.dgw[data-pane="diagram"] .dgw-main:has(.dgw-drawer) {
  grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
}

/* In list mode the table gets the whole width and scrolls in its
 * own box, so the columns stop wrapping to one character per
 * line. The canvas is hidden rather than shrunk: a 0-width canvas
 * would still be mounted and would still be measured by the
 * renderer at fit time. */
.dgw[data-pane="list"] .dgw-canvas-wrap { display: none; }
.dgw[data-pane="list"] .dgw-main {
  grid-template-columns: minmax(0, 1fr);
}
.dgw[data-pane="list"] .dgw-main:has(.dgw-drawer) {
  grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
}

/* ══ the validation readout discloses its warnings ═════════════ */

/* A count with no way to read the reason says something is wrong
 * and refuses to say what — worse than silence. Positioned like
 * the toolbar menus: absolute, so opening it cannot grow the
 * status strip and push the canvas. It opens UPWARD because the
 * status line is the last row on the page. */
.dgw-warnlist { position: relative; display: inline-block; }
.dgw-warnlist > summary { cursor: pointer; list-style: none; }
.dgw-warnlist > summary::-webkit-details-marker { display: none; }
.dgw-warnlist > summary::after { content: " \25b4"; }
.dgw-warn-items {
  position: absolute;
  z-index: 6;
  bottom: calc(100% + .25rem);
  left: 0;
  max-width: min(38rem, 90vw);
  max-height: 40vh;
  overflow-y: auto;
  margin: 0;
  padding: .5rem .5rem .5rem 1.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-overlay, 0 4px 12px rgba(0, 0, 0, .18));
  font-size: 13px;
}
.dgw-warn-items li { margin: 0 0 .25rem; }
.dgw-warn-error { color: var(--danger, #b91c1c); }

/* ── §4 addition: marker tokens (ADR-126, ADR-128) ───────────────
 *
 * For DESIGN.md §4's token block AND styles.css, in one turn —
 * design_tokens_test.py pins them to each other line by line.
 *
 * These five hex values are ADR-126's and ADR-128's, ported from
 * Original (`flowchart-v2/shapes.tsx`). This is the one place a
 * colour is decided; the island names none of them and emits class
 * names only.
 *
 * CONTRAST, computed rather than assumed — white glyph on fill:
 *
 *     risk               #b91c1c   6.47   text glyph `!`   AA ✓
 *     control-key        #e0443a   4.15   PATH (tick)      3:1 ✓
 *     control-automated  #15803d   5.02   PATH (play)      3:1 ✓
 *     control-proposed   #2563eb   5.17   text glyph `P`   AA ✓
 *     control-gap        #b45309   5.02   text glyph `?`   AA ✓
 *
 * `control-key` is BELOW AA's 4.5 and passes only because its glyph
 * is a path — a graphical object held to §4's 3:1 non-text
 * threshold, the same reasoning §4 already applies to
 * `--diagram-edge-muted`. **If anyone ever redraws that tick as a
 * text "✓", it drops below AA.** Worth a comment at the value.
 */

/* ── the badges themselves ───────────────────────────────────── */

.diagram-markers {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin-top: 2px;
}

.diagram-marker {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-family: var(--diagram-font);
  /* §4: node metadata is 11–12px. The record code beside the badge
   * is metadata, not a label. */
  font-size: 11px;
  color: var(--diagram-node-secondary);
  /* The badge is a TOUCH TARGET as well as a mark. The review
   * already found 26–30px against a 40/44 floor elsewhere; this
   * keeps the visual footprint small without shrinking the target
   * below what a finger can hit. */
  min-height: 20px;
}

.diagram-marker .diagram-marker-fill { fill: currentColor; }
.diagram-marker .diagram-marker-glyph { stroke: var(--diagram-node-surface); }
.diagram-marker .diagram-marker-glyph-text { fill: var(--diagram-node-surface); }

/* Each kind sets `color`, and the SVG inherits it through
 * `currentColor` — so one declaration per kind colours the fill,
 * the code beside it and anything added later, and no rule can
 * drift from its own badge. */
.diagram-marker-risk { color: var(--diagram-marker-risk); }
.diagram-marker-control { color: var(--diagram-marker-control); }
.diagram-marker-control-key { color: var(--diagram-marker-control-key); }
.diagram-marker-control-automated {
  color: var(--diagram-marker-control-automated);
}
.diagram-marker-control-proposed {
  color: var(--diagram-marker-control-proposed);
}
.diagram-marker-control-gap { color: var(--diagram-marker-control-gap); }

/* A PROPOSAL is outlined, not filled — it is not yet a fact. */
.diagram-marker.is-dashed .diagram-marker-fill {
  fill: var(--diagram-marker-control-proposed);
  stroke: currentColor;
  stroke-width: 0.5;
}

/* A GAP IS HOLLOW, and this is the rule most likely to be
 * "corrected" by somebody who has not read ADR-128. It marks a
 * control point this database holds NO control for — the only
 * marker derived from an ABSENCE, and mapping a control REMOVES
 * it. The unfilled centre is the point: a control-shaped hole
 * rather than a control. It is a real state, NOT an error state,
 * so it takes no danger colour and no warning iconography. */
.diagram-marker.is-hollow .diagram-marker-fill {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
}
.diagram-marker.is-hollow .diagram-marker-glyph-text {
  fill: currentColor;
}

/* Original's management-review star, kept because the concept is
 * theirs and practitioners already read it. */
.diagram-marker-star {
  color: var(--diagram-warning);
  font-size: 10px;
  line-height: 1;
}

.diagram-marker-id {
  /* The record's own code. Never renumbered by the renderer — two
   * diagrams of one process must cite the same thing. */
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ══ ADR-130: iconic controls, unshrunken targets ══════════════ */

/* The owner asked for "more icon controls than buttons taking up
 * real estate". The width goes; the HIT AREA does not. A UI
 * review measured these controls at 26–30px against 40px desktop
 * and 44px touch, so this is a floor being raised at the same
 * time the label is being hidden — padding is not the real estate
 * he meant. */
.dgw-viewctl .btn.is-iconic {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  min-height: 40px;
  padding: 0;
}

@media (pointer: coarse) {
  /* 44px where a finger is the pointer, which is the stricter of
   * the two thresholds and the one a desktop-only check misses. */
  .dgw-viewctl .btn.is-iconic { min-width: 44px; min-height: 44px; }
}

/* The icon carries the meaning visually; the name is still in the
 * button's text for the screen reader, the agent's catalogue and
 * the gate's annotation table. `.sr-only` is what keeps all three
 * true while the width comes off. */
.dgw-viewctl .btn.is-iconic .ico-svg {
  width: 18px;
  height: 18px;
}

/* ---- The evidence package (M-03 f25/f26, screens 02+05) ----
   Screen 02's matrix and screen 05's panel share one vocabulary:
   the five-state pills reuse the pill tones, gap and pending
   REASONS render as full-width rows (the bundle: a gap is
   displayed, never suppressed — the reason IS the content), and
   the disclosure block reuses .sod-banner, because C-35's "one
   detector, one banner" argument covers the cover disclosures
   too. Wide tables scroll inside .pkg-scroll; the page never
   scrolls sideways. */
.pkg-state {
  display: flex; gap: 8px; align-items: center;
  font-weight: 700; margin: 0 0 10px;
}
.pkg-scroll { overflow-x: auto; }
.pkg-table {
  border-collapse: collapse; width: 100%; font-size: 13px;
}
.pkg-table th, .pkg-table td {
  text-align: left; padding: 5px 10px 5px 0;
  border-bottom: 1px solid var(--line); vertical-align: top;
}
.pkg-reason td {
  font-size: 12px; padding-top: 2px;
  color: var(--text-secondary);
}
.pkg-reason.bad td {
  background: var(--danger-tint); color: var(--danger-ink);
}
.pkg-reason.warn td {
  background: var(--warn-tint); color: var(--warn-ink);
}
.pkg-req-list { list-style: none; margin: 4px 0 0; padding: 0; }
.pkg-req {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 12px; padding: 3px 0;
  border-bottom: 1px dotted var(--line);
}
.pkg-req.bad span:last-child {
  color: var(--danger-ink); font-weight: 700;
}
.pkg-req.ok span:last-child { color: var(--text-secondary); }
.pkg-req.optional span:first-child {
  color: var(--text-secondary);
}
.pkg-req-head {
  margin: 2px 0 0; font-size: 13px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.pkg-pre-list { list-style: none; margin: 4px 0 0; padding: 0; }
.pkg-pre {
  display: flex; gap: 10px; font-size: 13px; padding: 4px 0;
}
.pkg-pre-rule {
  font-weight: 700; min-width: 64px;
  font-variant-numeric: tabular-nums;
}
.pkg-pre.bad { color: var(--danger-ink); }
.pkg-pre.warn { color: var(--warn-ink); }
.pkg-hash {
  font-size: 11px; color: var(--text-secondary);
  word-break: break-all;
}
.pkg-chain { list-style: none; margin: 4px 0 0; padding: 0; }
.pkg-chain-row {
  display: flex; gap: 10px; align-items: baseline;
  font-size: 12px; padding: 3px 0;
}
.pkg-chain-row.superseded { opacity: 0.7; }

/* ---- Screen 04 decision point (M-03 f27, ADR-140) ----------
 * Existing tokens only; appended rather than interleaved because
 * styles.css is a shared file two other frontend branches touch
 * (E-104's structural contention — a new block merges, a
 * reorder does not). */
.cc-decide { display: flex; flex-direction: column; gap: 12px; }
.cc-decide-proposed {
  padding: 10px 12px; border-radius: var(--radius-panel);
  background: var(--surface-subtle); border: 1px solid var(--line);
}
.cc-decide-proposed p {
  margin: 4px 0 0; font-size: 13px; color: var(--text-secondary);
}
.cc-decide-label {
  font-size: 11px; font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text-muted);
}
.cc-decide-kinds { display: flex; gap: 6px; flex-wrap: wrap; }
.cc-decide-kind.on {
  border-color: var(--line-strong); font-weight: 700;
  background: var(--surface-subtle);
}
.cc-decide-indep {
  padding: 10px 12px; border-radius: var(--radius-panel);
  background: var(--warn-tint); border: 1px solid var(--warn-line);
  border-left: 4px solid var(--warn);
  font-size: 13px; color: var(--text-primary);
}
.cc-decide-indep[hidden] { display: none; }
.cc-decide-field {
  display: flex; flex-direction: column; gap: 4px; font-size: 13px;
}
.cc-decide-field > span { font-weight: 600; color: var(--text-primary); }
.cc-decide-field[hidden] { display: none; }
.cc-decide-rationale, .cc-decide-evidence {
  width: 100%; padding: 8px 10px; border: 1px solid var(--line);
  border-radius: var(--radius); font: inherit; box-sizing: border-box;
}
.cc-decide-count { font-size: 12px; color: var(--text-muted); }
.cc-decide-count.short { color: var(--danger); font-weight: 600; }

/* The owner's aggregate view of delegated review (ADR-140). Same
   token vocabulary as .cc-decide above — no lifted mockup palette;
   the layout follows Screen 04's structure on ControlBridge
   brand. .cc-roll-mount stays empty (renders nothing) on an
   owner-only control, so it costs no chrome there. */
.cc-roll { display: flex; flex-direction: column; gap: 12px; }
.cc-roll h4 {
  margin: 0 0 6px; font-size: 11px; font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-muted);
}
.cc-roll-note { margin: 0; font-size: 13px; color: var(--text-secondary); }
.cc-roll-rule {
  display: flex; align-items: center; gap: 8px; margin: 4px 0;
  font-size: 13px;
}
.cc-roll-arrow { color: var(--text-muted); }
.cc-roll-who { font-weight: 600; color: var(--text-primary); }
.cc-roll-default {
  margin: 6px 0 0; font-size: 12px; color: var(--text-muted);
}
.cc-roll-rev {
  display: flex; justify-content: space-between; gap: 12px;
  align-items: baseline; padding: 6px 0;
  border-top: 1px solid var(--line); font-size: 13px;
}
.cc-roll-prog { color: var(--text-secondary); }
.cc-roll-decrow {
  padding: 8px 0; border-top: 1px solid var(--line);
}
.cc-roll-decmeta {
  display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
  font-size: 13px;
}
.cc-roll-flag {
  padding: 1px 8px; border-radius: var(--radius);
  background: var(--warn-tint); border: 1px solid var(--warn-line);
  color: var(--warn-ink); font-size: 11px; font-weight: 600;
}
.cc-roll-decid { color: var(--text-muted); font-size: 12px; }
.cc-roll-form { margin-top: 8px; }
.cc-roll-form[hidden] { display: none; }
/* ---- Screen 05 alignment (M-03 f26, 2026-08-29) -----------
   The bundle's two-card shape in ControlBridge tokens — its
   layout and hierarchy, never its palette (product-owner
   instruction over the bundle README). The mockup's #0061FE
   is coincidentally --brand-mark, which DESIGN.md §5 reserves
   for the sign-in card alone — its meanings map to the state
   inks below, and the ordinary case stays unmarked
   (neutral base). Appended, never reordered:
   styles.css is a live seam for two other screens' strands. */
.pkg-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-panel); margin: 0 0 14px;
  overflow: hidden;
}
.pkg-card-head {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 8px 12px; padding: 12px 16px;
  background: var(--surface-subtle);
  border-bottom: 1px solid var(--line);
}
.pkg-card-title { font-weight: 700; font-size: 14px; margin: 0; }
.pkg-counts {
  margin-left: auto; font-size: 12px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.pkg-card-body { padding: 4px 16px 12px; }
.pkg-card-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 10px 20px;
  padding: 12px 16px; background: var(--surface-subtle);
  border-top: 1px solid var(--line);
}
.pkg-foot-note {
  font-size: 12px; line-height: 1.5;
  color: var(--text-secondary); max-width: 66ch;
}
.pkg-sub { margin: 14px 0 4px; font-size: 13px; }
.pkg-row-missing td { background: var(--danger-tint); }
.pkg-note { font-size: 12px; margin-top: 3px; }
.pkg-note.bad { color: var(--danger-ink); }
.pkg-note.warn { color: var(--warn-ink); }
.pkg-dim { color: var(--text-secondary); font-size: 12px; }
.pkg-attr-ids.pkg-attr-ids { /* neutral base: an ordinary
   binding is unmarked; only the MISSING case is coloured —
   colour everything and nothing is marked (Screen 01's
   pattern, relayed by the orchestrator 2026-08-29) */ }
.pkg-attr-ids.bad { color: var(--danger-ink); }
.pkg-chip {
  font-size: 11px; color: var(--text-secondary);
  background: var(--surface-subtle);
  border: 1px solid var(--line);
  padding: 2px 7px; border-radius: 4px;
}
.pkg-dot {
  flex: none; width: 18px; height: 18px; border-radius: 50%;
  font: inherit; font-weight: 700; font-size: 11px;
  line-height: 18px; text-align: center;
  margin-top: 1px;
}
.pkg-dot.ok {
  color: var(--success-ink); background: var(--success-tint);
}
.pkg-dot.warn {
  color: var(--warn-ink); background: var(--warn-tint);
}
.pkg-dot.bad {
  color: var(--danger-ink); background: var(--danger-tint);
}
.pkg-pre-list.pkg-pre-list { margin: 4px 16px; }
.pkg-pre-msg.warn { color: var(--warn-ink); }
.pkg-pre-msg.bad { color: var(--danger-ink); }
.pkg-panel .pkg-seal-note { margin: 4px 16px 10px; }

/* ==================== Control Execution ====================
 * The two Control Execution leaves — the executor chat and the
 * run view. **Tokens only, no new ones**: DESIGN.md §5/§6 is
 * pinned to this file token by token by design_tokens_test.py,
 * which recomputes the contrast ratios rather than believing
 * them, so a colour invented here would either fail the suite or
 * move a documented decision without a decision.
 *
 * The one place a raw colour appears is `.run-dot`, and it is set
 * from the STYLE ATTRIBUTE in control_run.js because the value
 * is the vagent node's own `color` read live from the engine. A
 * re-authored flow therefore looks the same here as in the
 * engine's editor, and this stylesheet has no opinion about it.
 */
.cx-panel { margin: 0 0 1rem; }

.cx-note,
.cx-muted { color: var(--text-secondary); font-size: 0.875rem; }

.cx-error {
  color: var(--danger-ink);
  background: var(--danger-tint);
  border: 1px solid var(--danger-line);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
}

/* The mock banner. Warn, not danger: nothing is broken — the
 * page is honest about being a surface. It is loud on purpose,
 * because a status that looks live and is not is the failure
 * this whole view is careful about. */
.cx-mock {
  color: var(--warn-ink);
  background: var(--warn-tint);
  border: 1px solid var(--warn-line);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  margin: 0 0 1.25rem;
}

/* ---- what a control expects ---- */
.cx-docs { list-style: none; margin: 0; padding: 0; }

.cx-docs li {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  margin: 0 0 0.5rem;
}

.cx-doc-id { margin: 0 0 0.25rem; }

.cx-doc-what { color: var(--text-primary); }

.cx-doc-src {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin-top: 0.25rem;
}

/* ---- the composer ---- */
.cx-composer {
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  padding: 0.75rem;
  background: var(--surface-subtle);
}

.cx-input {
  width: 100%;
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
  font: inherit;
  resize: vertical;
}

.cx-composer-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 0.5rem;
}

/* ---- the run tabs, joined to their pane ----
 * A browser tab strip: the ACTIVE tab and the panel below it are
 * one surface, and the tab breaks the rule that separates the
 * strip from the panel. Without that join a reader cannot tell
 * that the buttons and the workflow underneath belong to the
 * selected tab — which is exactly what was reported on
 * 2026-09-01 ("I don't see the border that this flow and
 * buttons is for this tab").
 *
 * The mechanics, in three parts:
 *   1. the STRIP draws the rule (`border-bottom`),
 *   2. each tab sits ON it (`margin-bottom: -1px`),
 *   3. the active tab paints over it (`box-shadow`), so the
 *      line is broken exactly under the selected tab.
 * The panel then has no top border of its own — the strip's
 * rule is its top edge, which is why they cannot drift apart.
 *
 * A TAB IS A RUN. Two runs of one control are ordinary, so the
 * label carries a start time as well as the code — see
 * `label()` in control_run.js.
 *
 * The close control is its own <button> beside the tab's face,
 * never nested inside it: a button in a button is invalid, and
 * "close" must be reachable by keyboard on its own.
 */
.run-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  margin: 0;
  padding: 0 0.5rem;
  max-width: 48rem;
  /* `--line-strong`, not `--line`: DESIGN.md §5 reserves it for
   * "a control's own edge", and a tab IS a control. `--line`
   * merely divides, and at #e2e8f0 on a #f1f5f9 page it is
   * nearly invisible — which is what made the strip read as
   * detached from its panel. */
  border-bottom: 1px solid var(--line-strong);
  position: relative;
  z-index: 1;
}

.run-tab {
  display: flex;
  align-items: stretch;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0;
  /* Unselected tabs sit on the PAGE ground so the selected one,
   * which shares the panel's --surface, is the only bright
   * thing in the strip.
   *
   * They keep a hairline edge rather than none. With five runs
   * of one control the labels differ only by `HH:MM`, and
   * edgeless labels merge into a single grey band that cannot
   * be parsed as separate tabs. */
  background: var(--background);
  border-color: var(--line);
  margin-bottom: -1px;
  overflow: hidden;
}

.run-tab:hover {
  background: var(--surface-subtle);
  border-color: var(--line-strong);
}

/* The selected tab IS the panel: same ground, and the 1px
 * shadow paints out the strip's rule beneath it so the two
 * surfaces are continuous. */
.run-tab.active {
  border-color: var(--line-strong);
  background: var(--surface);
  box-shadow: 0 1px 0 0 var(--surface);
}

.run-tab-face {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  border: 0;
  background: none;
  cursor: pointer;
  padding: 0.4375rem 0.5rem 0.4375rem 0.75rem;
  font: inherit;
  color: var(--text-muted);
}

.run-tab.active .run-tab-face {
  color: var(--text-primary);
  font-weight: 500;
}

/* Live or finished, at a glance — the reason a reader can leave
 * a tab in the background at all. The pulse is only on the live
 * side, so a still dot always means "nothing is happening here
 * any more". */
.run-tab-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  flex: 0 0 auto;
  background: var(--text-muted);
}

.run-tab-dot.live {
  background: var(--brand);
  animation: run-tab-pulse 1.6s ease-in-out infinite;
}

.run-tab-dot.over { background: var(--success); }
.run-tab-dot.s-error { background: var(--danger); }

/* Killed is over, not broken — the same decision as
 * `.st-killed`, and it has to be made twice because the strip
 * and the stage rows carry their own colours. Grey, so a
 * stopped tab reads as neither finished (green) nor failed
 * (red). Ordered after `.over` on purpose: equal specificity,
 * so source order is what lets a killed run outrank it. */
.run-tab-dot.s-killed,
.run-tab-dot.s-cancelled { background: var(--text-muted); }

@keyframes run-tab-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .run-tab-dot.live { animation: none; }
}

.run-tab-x {
  border: 0;
  background: none;
  cursor: pointer;
  padding: 0 0.5rem;
  font: inherit;
  line-height: 1;
  color: var(--text-muted);
}

.run-tab-x:hover {
  color: var(--danger-ink);
  background: var(--danger-tint);
}

/* ---- the panel the tabs open ----
 * VS Code's model: a tab owns a real panel, and switching only
 * shows or hides it. So a pane keeps its drawn stages and its
 * scroll position, and — the reason it is structural rather
 * than cosmetic — its buttons carry their own run's id and
 * cannot act on any other.
 *
 * No `border-top`: the strip's rule above IS this panel's top
 * edge. Giving it one too would draw a double line and let the
 * two drift apart.
 */
.run-panes {
  max-width: 48rem;
  border: 1px solid var(--line-strong);
  border-top: 0;
  border-radius: 0 0 var(--radius) var(--radius);
  /* `--surface`, the card ground — NOT `--background`, which is
   * the PAGE ground. Painting a panel the same colour as the
   * page behind it is why it read as no panel at all. */
  background: var(--surface);
  padding: 1rem;
}

.run-pane .run-head { margin-top: 0; }

.run-pane .run-graph { max-width: none; }

/* ---- an approval, inside its run's pane ----
 * Amber, because a parked run is waiting on a PERSON — it is
 * not an error and not a success, and the two existing tones
 * would both say the wrong thing. It sits above the stages so
 * the thing blocking the flow is read before the flow.
 */
.run-ask { margin: 0 0 1rem; }

.run-ask-card {
  border: 1px solid var(--warn-line);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  background: var(--warn-tint);
  padding: 0.75rem 0.875rem;
}

.run-ask-title {
  margin: 0 0 0.25rem;
  font-weight: 600;
  color: var(--warn-ink);
}

.run-ask-sum {
  margin: 0 0 0.625rem;
  font-size: 0.8125rem;
  color: var(--text-primary);
  white-space: pre-wrap;
}

.run-ask-opts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  align-items: center;
}

.run-ask-say {
  font-size: 0.8125rem;
  color: var(--text-muted);
  margin-left: 0.25rem;
}

/* ---- the run controls ----
 * One row bound to the ACTIVE tab, the way vagent's workflow
 * view does it — see the note in control_run.js on why they are
 * not repeated inside every tab chip.
 */
.run-bar {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin: 0 0 1rem;
}

/* Buttons that do not apply are HIDDEN, not greyed — vagent's
 * choice, and the right one: a greyed Resume beside a running
 * job invites a click that can never work. `[hidden]` needs no
 * rule of its own; the reset already honours it. */

/* Tone comes from the EXISTING button vocabulary
 * (`button.btn.ghost` / `.danger` above) — this file adds no
 * look of its own for them. Three solid brand buttons in a row
 * would say Stop and Kill are as affirmative as Start.
 */

/* Armed = the next click also kills a run in ANOTHER tab. It
 * has to read as a different button, not the same one with a
 * longer label. */
.run-bar-btn.armed {
  border-color: var(--danger);
  background: var(--danger-tint);
  color: var(--danger-ink);
}

.run-bar-say {
  color: var(--text-muted);
  font-size: 0.8125rem;
  margin-left: 0.25rem;
}

/* ---- the run graph ----
 * A vertical list, not a canvas. The diagram island (React Flow)
 * is sanctioned for editable, port-aware diagrams (ADR-066); a
 * run is a sequence somebody reads top to bottom, so reaching
 * for the island would buy nothing and spend the exception.
 */
.run-graph {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: 44rem;
}

.run-node {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 0.625rem 0.875rem;
  margin: 0 0 0.375rem;
  background: var(--background);
}

/* The engine's own node colour. Value comes from the inline
 * style attribute — see the header note. */
.run-dot {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  flex: 0 0 auto;
}

.run-node-main { display: flex; flex-direction: column; flex: 1; }

.run-node-label { color: var(--text-primary); }

.run-node-kind {
  color: var(--text-muted);
  font-size: 0.75rem;
}

.run-state {
  font-size: 0.8125rem;
  border-radius: var(--radius);
  padding: 0.125rem 0.5rem;
  white-space: nowrap;
}

/* The six states. Two carry a decision. `empty` is amber and
 * NOT green, deliberately: a stage that finished and produced
 * nothing is not a success — it is the failure the manager plan
 * calls "most worth catching", and colouring it green is how it
 * gets believed. `killed` is the mirror of that: it is NOT red,
 * because a kill is a deliberate stop and not an error path. */
.st-waiting .run-state {
  color: var(--text-muted);
  background: var(--surface-subtle);
}

.st-running { border-left-color: var(--brand); }
.st-running .run-state {
  color: var(--brand-dark);
  background: var(--brand-tint);
}

.st-done { border-left-color: var(--success); }
.st-done .run-state {
  color: var(--success-ink);
  background: var(--success-tint);
}

.st-empty { border-left-color: var(--warn); }
.st-empty .run-state {
  color: var(--warn-ink);
  background: var(--warn-tint);
}

/* Neutral and definite — deliberately not `st-failed`'s red,
 * and deliberately not `st-waiting`'s pale grey either, which
 * would read as "not started yet" for a stage that ran. */
.st-killed { border-left-color: var(--line-strong); }
.st-killed .run-state {
  color: var(--text-secondary);
  background: var(--surface-subtle);
}

.st-failed { border-left-color: var(--danger); }
.st-failed .run-state {
  color: var(--danger-ink);
  background: var(--danger-tint);
}

.run-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
}

/* The executor conversation. Two bubble tones only — the
 * Copilot's richer set (tool indicators, proposed blocks) is its
 * own; this view has one job and borrowing that vocabulary would
 * imply behaviour it does not have. */
.cx-msgs {
  max-height: 22rem;
  overflow-y: auto;
  margin: 0 0 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.cx-msg {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0.5rem 0.75rem;
  max-width: 46rem;
}

.cx-msg-me {
  align-self: flex-end;
  background: var(--brand-tint);
  border-color: var(--brand-line);
}

.cx-msg-bot { align-self: flex-start; background: var(--background); }

.cx-msg p:first-child { margin-top: 0; }
.cx-msg p:last-child { margin-bottom: 0; }

/* ==============================================================
   The Control Execution rail
   ==============================================================
   The executor's panel takes the Copilot's column inside the
   Control Execution section, and gives it straight back on the
   way out — the Copilot is HIDDEN here, never removed. It
   follows `.asst-panel`'s display pattern exactly so the column
   does not jump when the two swap; only the contents differ.
   The `[hidden]` rule comes last because executor_rail.js hides
   by the attribute, which must beat the open-state rule. */
.exec-panel { display: none; }
.layout.copilot-open .exec-panel {
  display: flex; flex-direction: column;
  height: 100%; min-width: 0; min-height: 0;
  background: var(--surface);
}
.exec-panel[hidden] { display: none; }

.exec-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}
/* Tabs in the head, copying vagent's `#wf-right-tabs`: the pane
   changes what it shows and the chrome around it does not move.
   Underline rather than a pill, so the active tab reads as part
   of the pane below it. */
.exec-tabs { display: flex; gap: 4px; }
.exec-tab {
  appearance: none; background: none; border: 0;
  border-bottom: 2px solid transparent;
  padding: 4px 10px 7px; font: inherit; font-size: 13px;
  color: var(--text-muted); cursor: pointer;
}
.exec-tab:hover { color: var(--text-primary); }
.exec-tab.active {
  color: var(--text-primary); font-weight: 600;
  border-bottom-color: var(--brand);
}

.exec-body { flex: 1 1 auto; min-height: 0; display: flex; }

/* ---- the chat ---------------------------------------------- */
.exec-chat {
  display: flex; flex-direction: column;
  flex: 1 1 auto; min-height: 0; min-width: 0;
}
.exec-log {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  padding: 12px 16px; display: flex;
  flex-direction: column; gap: 10px;
}
.exec-msg {
  padding: 8px 11px; border-radius: var(--radius);
  font-size: 13px; line-height: 1.5;
  max-width: 100%; overflow-wrap: anywhere;
}
.exec-msg-me { background: var(--brand-tint); align-self: flex-end; }
.exec-msg-bot { background: var(--msg-ai); align-self: flex-start; }
.exec-msg > :first-child { margin-top: 0; }
.exec-msg > :last-child { margin-bottom: 0; }

/* A MESSAGE IS NOT A SCROLL BOX.
 *
 * `setMarkdown` puts `.md` on the bubble itself, and
 * `.md { overflow-x: auto }` (above) therefore turned every
 * message into its own scroller — a scrollbar per bubble in a
 * 384px rail, with the text cut off inside it. A reply must be
 * readable in full by scrolling the CONVERSATION, which is what
 * `.exec-log` is for; nesting a scroller per message means the
 * reader has to find and drive one scrollbar per answer.
 *
 * `flex: 0 0 auto` is the other half: `.exec-log` is a flex
 * column, so without it a long message is COMPRESSED to fit
 * rather than making the log taller. */
.exec-msg.md { overflow: visible; }
.exec-msg { flex: 0 0 auto; }

/* Only genuinely wide children scroll, and they scroll inside
 * themselves — a code block or a table is a box whose width the
 * rail cannot negotiate, unlike a paragraph, which wraps. */
.exec-msg pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-x: visible;
}
.exec-msg table {
  display: block;
  width: max-content;
  max-width: 100%;
  overflow-x: auto;
}

/* ---- the interleaved tool marker, in a chat bubble ----------
   `> 🔧 **Bash**`, the same shape the node pane uses and now
   the same in the supervisor: `exec_chat.py` writes the marker
   into the turn's text, so it arrives as an ordinary
   blockquote and lands at the point the call happened.

   A rule down the side rather than a box — it is a landmark in
   the flow, not a section — and identical to
   `.exec-node-out blockquote` on purpose, so "the agent
   shelled out" looks the same wherever a reader meets it. */
.exec-msg blockquote {
  margin: 6px 0;
  padding: 0 0 0 8px;
  border-left: 2px solid var(--line-strong);
  color: var(--text-secondary);
}
/* Paragraph rhythm inside a bubble. Without it a reply with
   several paragraphs runs together, which is what made a long
   supervisor answer read as one block. */
.exec-msg p,
.exec-msg ul,
.exec-msg ol,
.exec-msg pre { margin: 6px 0; }
.exec-msg > :first-child { margin-top: 0; }
.exec-msg > :last-child { margin-bottom: 0; }

/* The uploaded files, immediately above the composer. Capped and
   scrollable rather than growing: the composer must never be
   pushed off the bottom of a 384px rail by twenty uploads. */
.exec-up {
  flex: 0 0 auto; border-top: 1px solid var(--line);
  max-height: 34%; display: flex; flex-direction: column;
}
.exec-up-head {
  display: flex; align-items: center; gap: 6px; width: 100%;
  appearance: none; background: none; border: 0; cursor: pointer;
  padding: 8px 16px 4px; text-align: left;
  font: inherit;
  font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted);
}
.exec-up-head:hover { color: var(--text-primary); }
.exec-up-caret {
  width: 0; height: 0; flex: 0 0 auto;
  border-left: 4px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transform: rotate(90deg);
  transition: transform var(--t-fast, .12s) ease;
}
.exec-up.collapsed .exec-up-caret { transform: rotate(0deg); }
/* Collapsed hides the rows AND the Clean files button, and gives
   the height back to the log — which is the point of collapsing
   it in a short rail. */
.exec-up.collapsed { max-height: none; }
.exec-up.collapsed .exec-up-body { display: none; }
.exec-up-count {
  margin-left: auto; text-transform: none; letter-spacing: 0;
  font-weight: 500; font-variant-numeric: tabular-nums;
}
.exec-up-body {
  display: flex; flex-direction: column; min-height: 0;
}
.exec-up-list {
  list-style: none; margin: 0; padding: 0 16px 4px;
  overflow-y: auto; min-height: 0;
}
.exec-up-row {
  display: flex; gap: 8px; align-items: baseline;
  font-size: 12px; padding: 0;
  border-bottom: 1px solid var(--line);
}
.exec-up-row:last-child { border-bottom: 0; }
/* A row that OPENS is a real <button>, so it has to be un-styled
   back into a row: the platform's default chip is what a button
   looks like, and a list of chips does not read as a list. The
   affordance is the hover and the focus ring, not a border. */
.exec-up-open {
  display: flex; gap: 8px; align-items: baseline; width: 100%;
  appearance: none; background: none; border: 0; cursor: pointer;
  font: inherit; font-size: 12px; text-align: left;
  padding: 5px 0; color: inherit; border-radius: var(--radius);
}
.exec-up-open:hover { background: var(--surface-subtle); }
.exec-up-open:hover .exec-up-name { color: var(--text-primary); }
/* Not `outline: none` — a keyboard reader needs to see where
   they are, and the rail is a list of identically-shaped rows. */
.exec-up-open:focus-visible {
  outline: 2px solid var(--brand); outline-offset: -2px;
}
/* A row with no id is not a button and must not look like one,
   but it still needs the same gutter so the list lines up. */
.exec-up-row > .exec-up-name { padding: 5px 0; }
.exec-up-name {
  color: var(--text-primary); flex: 1 1 auto;
  overflow-wrap: anywhere;
}
.exec-up-size {
  color: var(--text-muted); font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.exec-up-empty {
  padding: 4px 0; font-size: 12px; color: var(--text-muted);
}
.exec-up-act { flex: 0 0 auto; padding: 0 16px 8px; }
/* Quiet, because it destroys nothing: it empties a list this
   browser keeps and touches no uploaded file. */
.exec-up-clean {
  appearance: none; font: inherit; font-size: 12px;
  padding: 3px 10px; cursor: pointer;
  color: var(--text-secondary); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.exec-up-clean:hover:not(:disabled) {
  color: var(--text-primary);
  border-color: var(--line-strong);
}
.exec-up-clean:disabled { opacity: .5; cursor: default; }

.exec-input {
  flex: 0 0 auto; display: flex; gap: 8px; align-items: flex-end;
  padding: 10px 12px; border-top: 1px solid var(--line);
}
.exec-input textarea {
  flex: 1 1 auto; min-width: 0; resize: none;
  font: inherit; padding: 7px 9px;
  border: 1px solid var(--line); border-radius: var(--radius);
}
.exec-input-off { opacity: .55; }
.exec-input-off textarea { background: var(--surface-subtle); }

/* ---- nodes above, supervisor below -------------------------
   vagent's workflow arrangement. A column split rather than
   tabs: both halves belong to one run, and reading a node's
   output while asking the supervisor about it is the whole
   reason they sit together. */
.exec-split {
  display: flex; flex-direction: column;
  flex: 1 1 auto; min-height: 0; min-width: 0;
}
.exec-pane {
  display: flex; flex-direction: column;
  min-height: 0; padding: 12px 16px;
}
.exec-pane-nodes { flex: 1 1 55%; overflow-y: auto; }

/* The divider between the two halves, draggable like vagent's.
   It stopped being a plain rule the moment both halves carried
   real content: nodes grow with the graph and the supervisor
   holds a conversation, so which one gets the room is a trade
   only the reader can make.

   Nine pixels of grab area with a 1px line drawn inside it —
   the same shape as `.asst-resizer`, turned horizontal. A 1px
   target is not pointable, and it must still LOOK like a 1px
   rule. */
.exec-split-bar {
  flex: 0 0 auto; height: 9px; margin: -4px 0;
  position: relative; z-index: 2;
  cursor: row-resize; touch-action: none;
  border: 0; background: none; padding: 0; width: 100%;
}
.exec-split-bar::after {
  content: ""; position: absolute; inset: 4px 0 auto 0;
  height: 1px; background: var(--line);
  transition: background var(--t-fast) ease-out;
}
.exec-split-bar:hover::after,
.exec-split-bar:focus-visible::after,
.exec-split-bar.dragging::after { background: var(--brand); }
.exec-split-bar:focus-visible { outline: none; }

/* The supervisor's thread picker — vagent's `#wf-sup-bar`: a
   run may carry several supervisor threads, so choosing between
   them and starting another belong next to the chat itself. */
.exec-sup-bar {
  display: flex; align-items: center; gap: 6px;
  padding: 0 16px 8px; font-size: 12px;
  color: var(--text-muted);
}
.exec-sup-bar select {
  flex: 1 1 auto; min-width: 0; font: inherit;
  padding: 3px 6px; border: 1px solid var(--line);
  border-radius: var(--radius);
}
.exec-sup-new {
  appearance: none; background: none; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius);
  width: 24px; height: 24px; font: inherit; line-height: 1;
  color: var(--text-muted);
}
.exec-sup-bar select:disabled,
.exec-sup-new:disabled { opacity: .5; cursor: default; }

/* The run's status as the ENGINE reports it, beside the
   supervisor that talks about it. It is here because the
   supervisor misreports a finished run as running (vagent
   defect, 2026-09-03), and a reader should not have only the
   conversation's word for it. */
.exec-sup-run {
  flex: 0 0 auto; font-size: 11px; letter-spacing: .02em;
  color: var(--text-muted); white-space: nowrap;
}
.exec-sup-run.s-done { color: var(--success-ink); }
.exec-sup-run.s-running { color: var(--brand-dark); }
.exec-sup-run.s-error { color: var(--danger-ink); }
.exec-sup-run.s-killed,
.exec-sup-run.s-cancelled { color: var(--text-secondary); }
.exec-pane-super {
  flex: 1 1 45%; padding: 12px 0 0;
  /* No `border-top`: the draggable bar above now draws the
     line, and two rules 1px apart read as a 2px divider. */
}

/* The supervisor's conversation. **It had no rule at all**, and
   that is why it was invisible: `.exec-log` is `flex: 1;
   min-height: 0`, which resolves to ZERO height unless its
   parent is a flex column that fills. The pane was wired, sent
   messages and streamed replies into a box nobody could see
   (reported 2026-09-03). */
#exec-sup-body {
  display: flex; flex-direction: column;
  flex: 1 1 auto; min-height: 0;
}
#exec-sup-body .exec-log { padding: 0 16px 8px; }
#exec-sup-body .exec-input { padding: 8px 16px 12px; }
.exec-pane-super .exec-pane-title,
.exec-pane-super .exec-stub { padding: 0 16px; }
.exec-pane-title {
  font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text-muted);
  margin: 0 0 8px;
}
.exec-stub {
  font-size: 13px; line-height: 1.5; color: var(--text-primary);
  margin: 0 0 8px;
}
.exec-stub-why {
  font-size: 12px; line-height: 1.5; color: var(--text-muted);
  margin: 0;
}

/* The run toolbar — start a control, and the id it produced.
   The run id is shown because it is the handle for everything
   else: the supervisor thread, the engine's own views, and any
   question afterwards about which execution is being discussed. */
.run-bar {
  display: flex; align-items: center; gap: 12px;
  flex-wrap: wrap;
}
.run-id { font-size: 13px; color: var(--text-muted); }
.run-id code { font-family: var(--font-mono); }

/* The Nodes pane — a run's step transcript, read-only.
   Monospace for the output because it is engine text (tables,
   paths, ids), and wrapping it as prose makes it unreadable. */
.exec-node-list { list-style: none; margin: 0; padding: 0; }
.exec-node-row {
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.exec-node-row:last-child { border-bottom: 0; }
.exec-node-name {
  font-size: 12px; font-weight: 600;
  color: var(--text-primary);
}
.exec-node-status {
  font-size: 11px; color: var(--text-muted); margin-left: 6px;
}
/* A NODE'S OUTPUT IS NOT A SCROLL BOX.
 *
 * It used to be capped at 8em with its own scrollbar, so a pane
 * of three nodes was three nested scrollers inside the pane's
 * own — the reader had to find and drive the right one to read
 * an answer. The PANE scrolls (`.exec-pane-nodes`); the output
 * inside it shows in full. Same rule as the chat bubbles above.
 */
.exec-node-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 2px 0 4px;
}

/* A tool call is a fact about the run, not an action: no
   hover, no pointer, nothing that invites a click. Sized
   below the output text so a chatty node does not read as
   more important than what it produced. */
.exec-node-tool {
  font-size: 11px;
  line-height: 1.4;
  padding: 1px 6px;
  border-radius: 10px;
  border: 1px solid var(--line);
  color: var(--text-muted);
  background: var(--surface-subtle);
  white-space: nowrap;
}

/* A node's output is the thing the reader came here for, so it
   is body text now rather than a faint monospace aside:
   it reads at `--text-primary`, the same weight as body copy
   anywhere else in the app. It went muted (#94a3b8) ->
   secondary (#5a6a7e) -> primary (#0f172a) on 2026-09-09, in
   two passes, because each grey still read as an aside — and
   in a narrow rail a whole paragraph of grey is harder again
   than one grey label. Markdown-rendered the same day;
   `white-space: pre-wrap` is GONE deliberately, because with
   real block elements it doubled every gap and made one
   answer read as one long line. */
.exec-node-out {
  margin-top: 4px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-primary);
  overflow-wrap: anywhere;
}

/* "Working on it", for a node that has started and not yet
   said anything. vagent waves its wordmark's letters; three
   dots carry the same stagger without fifteen animated glyphs
   in a narrow rail. Same 1.4s as `sk-pulse`, the rail's
   existing waiting rhythm, so two waits on screen do not beat
   against each other. */
.exec-node-wait {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 0;
}

.exec-node-dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--desk-accent);
  opacity: 0.25;
  animation: exec-node-wave 1.4s ease-in-out infinite;
}

@keyframes exec-node-wave {
  0%, 100% { opacity: 0.2; transform: translateY(0); }
  50% { opacity: 1; transform: translateY(-2px); }
}

/* Honour a reader who asked for less motion: the dots stay,
   the movement goes. A silent node still needs to look
   different from a finished empty one. */
@media (prefers-reduced-motion: reduce) {
  .exec-node-dot {
    animation: none;
    opacity: 0.55;
  }
}

/* Markdown blocks inside a node's output. Tight but not
   collapsed: paragraphs need to be separable at a glance in a
   narrow rail. */
.exec-node-out > :first-child { margin-top: 0; }
.exec-node-out > :last-child { margin-bottom: 0; }

.exec-node-out p,
.exec-node-out ul,
.exec-node-out ol,
.exec-node-out pre {
  margin: 6px 0;
}

.exec-node-out pre,
.exec-node-out code {
  font-family: var(--font-mono);
  font-size: 11px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The interleaved tool marker (`> 🔧 **Bash**`), vagent's own
   shape. A rule down the side rather than a box: it is a
   landmark in the flow, not a section. */
.exec-node-out blockquote {
  margin: 6px 0;
  padding: 0 0 0 8px;
  border-left: 2px solid var(--line-strong);
  color: var(--text-secondary);
}

/* ---- an approval, inline in the node pane -------------------
   A DECISION IS A ROW LIKE ANY OTHER. It sits in run order
   among the skill nodes, so a reader follows one column and
   sees where the run stopped to ask — rather than reading the
   nodes here and the question in a popup somewhere else.

   `<details>` does the collapsing: native, keyboard reachable,
   and it holds its own state across a repaint. Open while the
   run is waiting (that is the reason nothing is moving);
   folded once answered, so six settled decisions do not bury
   the output between them. */
.exec-appr { margin-top: 4px; font-size: 12px; }
.exec-appr-sum {
  cursor: pointer; color: var(--text-secondary);
  font-size: 11px; padding: 2px 0;
}
.exec-appr-sum:hover { color: var(--text-primary); }
/* An approval whose summary skill produced nothing. Said in the
   card's own voice so the ADR-113 note below is not mistaken
   for the request. */
.exec-appr-empty {
  margin: 0 0 6px; font-size: 12px; line-height: 1.45;
  color: var(--text-primary); font-style: italic;
}

/* ---- IN THE RAIL, AN APPROVAL IS A CHAT BUBBLE --------------
   The shared builders are styled for a full-width run page: a
   13px card with a 4px amber rail down its side and its own
   margins. Dropped into a 384px conversation it read as a
   pasted-in panel rather than part of the exchange.

   So both cards take `.exec-msg`'s geometry — same padding,
   same `--radius`, same 12px/1.5 type as the messages above
   them — and the heavy left rail goes. Product-owner
   direction, 2026-09-10: *"align with chat style"*.

   **NO WARN TINT, and the amber was a wrong call I made
   first.** The pending card kept `--warn-tint` on the argument
   that a blocking decision should stand out; in a slim
   conversation it read as *"that yellow thing"* — product
   owner, 2026-09-10 — while the answered card, on the bot
   bubble's own ground, was already right. Urgency is carried by
   the row's own *"waiting for you"* and by the buttons being
   there at all; it does not need a colour that nothing else in
   the rail uses.

   **AND IT MUST BE SLIM.** The shared panel stacks a heading, a
   question, the summary, an ADR-113 note and the buttons — five
   blocks, which beside 12px chat bubbles read like *"an
   elephant"*. Two of those five are redundant HERE and only
   here: the row above already says which node this is and that
   it is waiting, so the panel's own heading and question line
   are saying it twice. They are hidden by CSS rather than
   dropped from the builder, because on the run page — where
   there is no row saying it — they are the only thing that
   does. */
.exec-appr-body .run-approval,
.exec-appr-body .run-decided {
  margin: 4px 0 2px;
  padding: 7px 9px;
  border: 0; border-left: 0;
  border-radius: var(--radius);
  font-size: 12px; line-height: 1.45;
  background: var(--msg-ai);
}
/* The row's name and status already say both of these. */
.exec-appr-body .run-approval > b,
.exec-appr-body .run-approval-q,
.exec-appr-body .run-decided-q { display: none; }
/* CHAT WEIGHT, NOT PANEL WEIGHT. The shared builder puts the
   summary at `--text-secondary`, which is right on a run page
   where it sits under a bold question — and wrong here, where
   it IS the message and everything around it in the rail is
   `--text-primary`. Same correction `.exec-node-out` needed
   for the same reason.

   (A larger, pure-black treatment was tried on 2026-09-10 and
   REVERTED the same day: the card it was judged against turned
   out to be an approval with no request text at all, so the
   only sentence on screen was the ADR-113 note — "the approval
   node I sent you with screenshot is just empty".) */
.exec-appr-body .run-approval-summary,
.exec-appr-body .run-decided-summary,
.exec-appr-body .run-decided-pick {
  margin: 0; color: var(--text-primary);
}
/* ADR-113's boundary still renders — answering resumes a
   process and signs nothing — but as a footnote rather than a
   paragraph competing with the question. */
.exec-appr-body .run-approval-note {
  margin: 5px 0 0; font-size: 10px; line-height: 1.35;
  color: var(--text-muted);
}
.exec-appr-body .run-approval-opts { gap: 6px; margin-top: 7px; }
/* Buttons sized for the rail. They carry the engine's own
   labels, which can be long, so they still wrap rather than
   clip — a button nobody can read is a decision nobody can
   make safely. */
.exec-appr-body .run-approval-opts .btn {
  font-size: 12px; padding: 4px 9px;
}
.exec-appr-body .run-approval-say {
  margin: 5px 0 0; font-size: 11px; min-height: 0;
}
/* Empty until an answer is in flight; it should not reserve a
   line in a slim card for the whole time it is not. */
.exec-appr-body .run-approval-say:empty { display: none; }

/* ---- three lines, then an ellipsis -------------------------
   Product-owner direction, 2026-09-10: a request in the rail
   may take about three lines; a longer one is read in the
   centre-screen dialog.

   **Scoped to the rail body ON PURPOSE.** The dialog MOVES this
   very element rather than copying it (one approval, one set of
   answer buttons), so the clamp has to be a property of WHERE
   the panel is, not of the panel. Leaving `.appr-clamped`
   unscoped would carry the clamp into the dialog and the "read
   the full request" would show three lines again. */
.exec-appr-body.appr-clamped .run-approval-summary {
  display: -webkit-box; -webkit-box-orient: vertical;
  -webkit-line-clamp: 3; line-clamp: 3;
  overflow: hidden;
}
.appr-more {
  display: block; margin: 6px 0 0; padding: 0;
  font: inherit; font-size: 11px; font-weight: 700;
  color: var(--brand-dark); background: none; border: 0;
  cursor: pointer; text-align: left;
}
.appr-more:hover { text-decoration: underline; }
/* Inside the dialog there is nothing left to expand. Same
   trick as the clamp: the element is unchanged, its
   surroundings are not. */
.appr-modal .appr-more { display: none; }
/* The clamp is keyed off the rail body, and the dialog is not
   inside it — so a panel moved there is full length with no
   class to toggle. */

/* ---- the centre-screen decision ----------------------------
   **A REVERSAL, RECORDED.** The frozen stack above is
   non-modal by a product-owner decision of 2026-08-30, on the
   grounds that a card must not cover what somebody just asked
   for. This dialog is the opposite and it is the same owner's
   call, 2026-09-10 — but note the difference that makes it
   consistent: this one is opened BY A CLICK, so covering the
   page is what was asked for, and it goes away on a click
   anywhere outside it, on Hide, or on Escape.

   Z-INDEX 950: above the frozen stack (890) and the drawer
   (900), because the reader deliberately opened it; below
   `.cc-modal` (1000), which is a true modal and must stay on
   top. `aria-modal="false"` and no focus trap — nothing here
   inerts the page, so the number is the whole of the
   precedence. */
.appr-modal { position: fixed; inset: 0; z-index: 950; }
.appr-modal-back {
  position: absolute; inset: 0;
  /* The literal `.cc-modal` already uses, deliberately: there
     is no scrim token in this stylesheet, and the rule these
     comments keep is that no token is invented here. Two
     dialogs sharing one scrim value is the point. */
  background: rgba(15, 23, 42, .45);
}
.appr-modal-card {
  position: relative;
  /* CENTRED, matching `.frozen-stack` above — the two dialogs
     are the same object to a reader, so they are the same
     object here: one width, one padding, one surface. */
  width: min(760px, calc(100vw - 48px));
  max-height: calc(100vh - 64px); overflow-y: auto;
  top: 50%; transform: translateY(-50%);
  margin: 0 auto;
  padding: 14px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-overlay);
  display: flex; flex-direction: column; align-items: stretch;
}
/* Same header shape as the frozen dialog: title left, close
   top right. */
.appr-modal-head {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 10px;
}
.appr-modal-title {
  flex: 1 1 auto; min-width: 0;
  font-size: 13px; font-weight: 700; color: var(--text-primary);
}
/* Chrome, deliberately quiet: it sits beside a decision, and a
   close control with any visual weight competes with the answer
   buttons below it. */
.appr-modal-hide {
  flex: 0 0 auto; font-size: 18px; line-height: 1;
}
/* THE PANEL IS THE CARD HERE TOO. The rail's slim overrides are
   keyed off `.exec-appr-body`, which this is not inside, so the
   panel comes back full size — and a bordered, tinted box
   inside a bordered dialog is a box in a box. This was the last
   place the amber survived, because the reset was written for
   `.frozen-stack` and not for here. */
.appr-modal-body .run-approval {
  margin: 0; padding: 0;
  background: none; border: 0;
}
/* The dialog's header already titles this. */
.appr-modal-body .run-approval > b { display: none; }
.appr-modal-body .run-approval-q { margin: 0; }
/* It is the whole content of the dialog, so it reads at full
   strength rather than as a caption. */
.appr-modal-body .run-approval-summary { color: var(--text-primary); }

/* The control picker beside the Run button. */
.run-control {
  font: inherit; padding: 5px 8px;
  border: 1px solid var(--line); border-radius: var(--radius);
  max-width: 22em;
}

/* ==============================================================
   MY WORK — the Command Center's centre, built to the wireframe
   canvas (2026-09-04).

   SCOPED ON PURPOSE. Every rule below is under `.desk`, and the
   canvas's tokens are declared on `.desk` rather than on
   `:root`. The left navigation and the right chat panel keep the
   interface's own palette; only this column takes the canvas's.
   Declaring these on `:root` would repaint the whole app, which
   is exactly the change the product owner ruled out.

   Every token used here is DEFINED here. An undefined custom
   property resolves to nothing rather than erroring — a border
   silently disappears — and `design_tokens_test.py` fails on a
   dangling `var()` for that reason.
   ============================================================== */
.desk {
  /* surfaces */
  --desk-page: #f7f8fa;
  --desk-card: #ffffff;
  --desk-line: #e6e9ef;
  --desk-line-strong: #d3d8e2;
  --desk-tint: #f9fafc;
  /* ink */
  --desk-heading: #0f1b33;
  --desk-body: #33415c;
  --desk-muted: #64748b;
  --desk-subtle: #94a3b8;
  /* accents */
  --desk-accent: #1d4ed8;
  --desk-accent-soft: #eef3ff;
  --desk-warn: #c2620d;
  --desk-warn-soft: #fff8f1;
  /* shape */
  --desk-radius: 10px;
  --desk-radius-sm: 7px;
  --desk-pill: 999px;
  --desk-shadow: 0 1px 2px rgba(15, 27, 51, .06);

  display: flex; flex-direction: column; gap: 18px;
  max-width: 1180px;
  color: var(--desk-body);
}

/* ---- header ---- */
.desk-eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--desk-subtle);
}
.desk-title {
  margin: 6px 0 5px; font-size: 30px; font-weight: 700;
  letter-spacing: -.02em; line-height: 1.1;
  color: var(--desk-heading);
}
.desk-lede {
  margin: 0; font-size: 14px; color: var(--desk-muted);
  text-wrap: pretty;
}

/* ---- stat band ---- */
.desk-stats {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px;
}
.desk-stat {
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  padding: 14px 16px;
}
.desk-stat-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px;
}
.desk-stat-label {
  font-size: 12.5px; font-weight: 600; color: var(--desk-body);
}
.desk-stat-icon { display: inline-flex; color: var(--desk-subtle); }
.desk-stat-icon svg { width: 15px; height: 15px; }
.desk-stat-value {
  margin-top: 6px; font-size: 30px; font-weight: 700;
  line-height: 1.05; color: var(--desk-heading);
}
.desk-stat.warn .desk-stat-value { color: var(--desk-warn); }
.desk-stat-foot {
  margin-top: 4px; font-size: 12px; color: var(--desk-muted);
}

/* ---- lane pills ---- */
.desk-filter {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 4px; background: var(--desk-tint);
  border: 1px solid var(--desk-line);
  border-radius: var(--desk-pill); width: fit-content;
}
.desk-pill {
  font: inherit; font-size: 13px; font-weight: 600;
  padding: 6px 15px; border: 0; border-radius: var(--desk-pill);
  background: transparent; color: var(--desk-muted);
  cursor: pointer;
}
.desk-pill:hover { color: var(--desk-heading); }
.desk-pill.on {
  background: var(--desk-card); color: var(--desk-heading);
  box-shadow: var(--desk-shadow);
}
.desk-pill:focus-visible {
  outline: 2px solid var(--desk-accent); outline-offset: 2px;
}

/* ---- group ---- */
.desk-group {
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  overflow: hidden;
}
.desk-group-head {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 18px; border-bottom: 1px solid var(--desk-line);
}
.desk-group-title {
  margin: 0; font-size: 17px; font-weight: 700;
  letter-spacing: -.01em; color: var(--desk-heading);
}
.desk-group-sub {
  margin: 3px 0 0; font-size: 12.5px; color: var(--desk-muted);
}
.desk-empty {
  padding: 22px 18px; font-size: 13px; color: var(--desk-muted);
}

/* ---- row ---- */
.desk-row {
  display: flex; align-items: center; gap: 14px;
  padding: 13px 18px; border-bottom: 1px solid var(--desk-line);
  cursor: pointer; background: var(--desk-card);
}
.desk-row:last-child { border-bottom: 0; }
.desk-row:hover { background: var(--desk-tint); }
.desk-row:focus-visible {
  outline: 2px solid var(--desk-accent); outline-offset: -2px;
}
.desk-row.late { background: var(--desk-warn-soft); }
.desk-row-icon {
  flex: none; width: 30px; height: 30px;
  border-radius: var(--desk-radius-sm); background: var(--desk-tint);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--desk-muted);
}
.desk-row-icon svg { width: 15px; height: 15px; }
.desk-row-body { flex: 1 1 auto; min-width: 170px; }
.desk-row-line {
  display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
}
.desk-row-id {
  font-family: var(--font-mono); font-size: 11.5px;
  font-weight: 600; color: var(--desk-accent);
}
.desk-row-title {
  font-size: 14px; font-weight: 600; color: var(--desk-heading);
  text-wrap: pretty;
}
.desk-row-ctx {
  margin-top: 4px; font-size: 12px; color: var(--desk-muted);
  text-wrap: pretty;
}
.desk-row-due {
  flex: none; width: 110px; text-align: right;
  font-size: 12px; font-weight: 600; color: var(--desk-body);
}
.desk-row-due.late { color: var(--desk-warn); }
.desk-row-act { flex: none; display: inline-flex; }

/* ---- Run pages: the log and the packet, on their own page ----
 * Product owner, 2026-09-09. Drawn to the Control Execution
 * Management screens in
 * devdocs/design/UI/control-execution-module/.
 *
 * UNIVERSAL, on the product owner's instruction 2026-09-09:
 * *"all execution logs and evidence trails should render with
 * the same design and detail"*. It was scoped to the run pages
 * for one day; the drawer, the workpaper, the repository and the
 * six other hosts now get the same treatment, because two looks
 * for one record is how a reader comes to distrust both.
 *
 * The drawer is the one NARROW host, and it gets the design's own
 * direction B — a single stream — rather than a second design.
 * Same rules, one layout prop, exactly as that document
 * recommends.
 *
 * Colour comes from DESIGN.md §5's semantic inks, every one
 * AA-verified on surface AND on its own tint. Not from the
 * artifacts' #0061FE family: that hex is `--brand-mark`, which
 * §5 reserves for the sign-in card and calls "never an interface
 * accent".
 */
.desk-runpage { display: flex; flex-direction: column; gap: 16px; }

/* the run header, as a card */
.run-view .run-head {
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  padding: 16px 20px; display: flex; flex-wrap: wrap; gap: 10px 28px;
}
.run-view .run-head .kv { display: flex; flex-direction: column; gap: 3px; }
.run-view .run-head .k {
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--desk-subtle);
}
.run-view .run-ok { color: var(--success-ink); font-weight: 600; }

/* the attribute tally and the stage rail */
.run-view .run-cov {
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  overflow: hidden;
}
.run-view .run-cov-head {
  margin: 0; padding: 14px 20px; font-weight: 600;
  color: var(--desk-heading); border-bottom: 1px solid var(--desk-line);
  background: var(--desk-tint);
}
.run-view .run-rail {
  display: flex; flex-wrap: wrap; margin: 0; padding: 0; list-style: none;
}
.run-view .run-rail-stage {
  flex: 1 1 132px; padding: 11px 15px;
  border-left: 3px solid var(--success-ink);
  border-bottom: 1px solid var(--desk-line);
}
.run-view .run-rail-stage.has-gap { border-left-color: var(--danger-ink); }
.run-view .run-rail-name {
  display: block; font-size: 12.5px; font-weight: 700;
  color: var(--desk-heading);
}
.run-view .run-rail-n {
  display: block; margin-top: 2px; font-size: 11.5px;
  color: var(--desk-muted); font-variant-numeric: tabular-nums;
}

/* the timeline, on the design's three-column row */
.run-view .run-timeline {
  margin: 0; padding: 0; list-style: none;
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  overflow: hidden;
}
/* The base rule is flex with an ABSOLUTELY POSITIONED dot, so a
 * grid here puts the one in-flow child — the body — in the first
 * track and squeezes the row to its width. Keep the flex, take
 * the timeline's connecting rail off, and let the actor lane's
 * own left border carry the state instead. */
.run-view .run-ev {
  padding: 0; border-left: 0;
  border-bottom: 1px solid var(--desk-line);
}
.run-view .run-ev:last-child { border-bottom: 0; }
.run-view .run-dot { display: none; }
.run-view .run-ev-body {
  padding: 14px 18px 14px 15px;
  border-left: 3px solid var(--brand);
}
.run-view .run-ev-bad .run-ev-body {
  border-left-color: var(--danger-ink);
}
.run-view .run-ev-head {
  display: grid; grid-template-columns: minmax(0, 168px) minmax(0, 1fr) minmax(0, 176px);
  gap: 12px; align-items: baseline;
}
.run-view .run-actor {
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand-dark);
}
.run-view .run-actor-human { color: var(--desk-heading); }
.run-view .run-step {
  display: block; margin-top: 3px; font-size: 11.5px;
  font-weight: 400; letter-spacing: 0; text-transform: none;
  color: var(--desk-muted); word-break: break-word;
}
.run-view .run-ev-title {
  font-size: 14px; font-weight: 600; color: var(--desk-heading);
}
.run-view .run-when {
  text-align: right; font-size: 11.5px; color: var(--desk-subtle);
  font-variant-numeric: tabular-nums;
}
.run-view .run-ev-meta {
  margin-top: 9px; display: flex; gap: 6px; flex-wrap: wrap;
  align-items: center;
}
.run-view .run-attrs, .run-view .run-det {
  font-size: 11px; font-weight: 600; padding: 3px 8px;
  border-radius: var(--desk-radius-sm);
  background: var(--brand-tint); color: var(--brand-dark);
}
.run-view .run-attrs-none {
  background: var(--desk-tint);
  color: var(--desk-muted); font-style: normal;
}
.run-view .run-det-inferential {
  background: var(--warn-tint); color: var(--warn-ink);
}
/* One markup path: the narration ships on every surface and only
 * this layout shows it. The trail keeps its own copy for the
 * drawer and the workpaper, and here that copy is hidden so the
 * expander carries the TECHNICAL half alone, as the design has
 * it. */
/* Shown EVERYWHERE, product owner 2026-09-09: every
 * execution log renders with the same design and the same
 * detail, whichever surface hosts it. */
.run-view .run-ev-say {
  display: block; margin-top: 8px; font-size: 14px;
  line-height: 1.6; color: var(--desk-body); max-width: 88ch;
}
.run-view .run-ev-say > .md > :first-child { margin-top: 0; }
.run-view .run-ev-say > .md > :last-child { margin-bottom: 0; }
.run-view .run-trail > .run-detail { display: none; }
/* The drawer is a NARROW host. The three-column head is the
 * design's own direction A, which its notes say costs
 * horizontal room; B — a single stream — is what that
 * document recommends for a narrow column. Same design,
 * same detail, one layout prop. */
.drawer .run-view .run-ev-head,
.drawer .pkg-panel .pkg-stats { display: block; }
.drawer .run-view .run-when { text-align: left; }
.run-view .run-trail { margin-top: 10px; }
.run-view .run-trail > summary {
  font-size: 11.5px; font-weight: 600; color: var(--brand);
  cursor: pointer;
}
.run-view .run-detail {
  margin-top: 9px; border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius-sm); background: var(--desk-tint);
  padding: 13px 15px; overflow-x: auto;
}

/* ---- the evidence packet, on a page ---- */
.pkg-panel .pkg-panel { display: flex; flex-direction: column; gap: 14px; }
.pkg-panel .pkg-stats {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 1px; background: var(--desk-line);
  border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); overflow: hidden;
}
.pkg-panel .pkg-tile {
  background: var(--desk-card); padding: 14px 16px;
  display: flex; flex-direction: column; gap: 5px;
}
.pkg-panel .pkg-tile-v {
  font-size: 26px; font-weight: 800; line-height: 1;
  letter-spacing: -.02em; color: var(--desk-heading);
  font-variant-numeric: tabular-nums;
}
.pkg-panel .pkg-tile-v.ok { color: var(--success-ink); }
.pkg-panel .pkg-tile-v.bad { color: var(--danger-ink); }
.pkg-panel .pkg-tile-l {
  font-size: 12.5px; font-weight: 600; color: var(--desk-heading);
}
.pkg-panel .pkg-tile-n {
  font-size: 11.5px; line-height: 1.45; color: var(--desk-muted);
}
.pkg-panel .pkg-state {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 0; font-size: 15px; font-weight: 600;
  color: var(--desk-heading);
}
@media (max-width: 1000px) {
  .pkg-panel .pkg-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .pkg-panel .pkg-stats { grid-template-columns: minmax(0, 1fr); }
}
.pkg-panel .pkg-card {
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  overflow: hidden;
}
.pkg-panel .pkg-card-head {
  padding: 14px 20px; border-bottom: 1px solid var(--desk-line);
  background: var(--desk-tint); display: flex; flex-wrap: wrap;
  align-items: center; justify-content: space-between; gap: 12px;
}
.pkg-panel .pkg-card-title {
  font-size: 13.5px; font-weight: 600; color: var(--desk-heading);
}
.pkg-panel .pkg-counts {
  font-variant-numeric: tabular-nums; color: var(--desk-muted);
  font-size: 12.5px;
}
.pkg-panel .pkg-card-body { padding: 0; }
.pkg-panel .pkg-scroll { overflow-x: auto; }
.pkg-panel .pkg-table { width: 100%; border-collapse: collapse; }
.pkg-panel .pkg-table th {
  text-align: left; padding: 9px 18px; background: var(--desk-tint);
  border-bottom: 1px solid var(--desk-line);
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--desk-subtle);
}
.pkg-panel .pkg-table td {
  padding: 11px 18px; border-bottom: 1px solid var(--desk-line);
  font-size: 12.5px; vertical-align: top;
}
.pkg-panel .pkg-row-missing { background: var(--danger-tint); }
.pkg-panel .pkg-attr-ids {
  font-family: ui-monospace, Menlo, monospace; font-size: 11px;
  color: var(--brand-dark);
}
.pkg-panel .pkg-attr-ids.bad { color: var(--danger-ink); }
.pkg-panel .pkg-note.bad { color: var(--danger-ink); }
.pkg-panel .pkg-pre-list { padding: 4px 20px 12px; }
.pkg-panel .pkg-pre {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 11px 0; border-bottom: 1px solid var(--desk-line);
}
.pkg-panel .pkg-pre:last-child { border-bottom: 0; }
.pkg-panel .pkg-dot {
  flex: none; width: 18px; height: 18px; border-radius: 999px;
  margin-top: 1px;
}
.pkg-panel .pkg-dot.ok { background: var(--success-tint); }
.pkg-panel .pkg-dot.bad { background: var(--danger-tint); }
.pkg-panel .pkg-pre-rule {
  font-family: ui-monospace, Menlo, monospace; font-size: 11px;
  color: var(--desk-subtle);
}
.pkg-panel .pkg-pre-msg.ok { color: var(--desk-body); }
.pkg-panel .pkg-pre-msg.bad { color: var(--danger-ink); }
.pkg-panel .pkg-card-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 16px;
  padding: 15px 20px; background: var(--desk-tint);
  border-top: 1px solid var(--desk-line);
}
.pkg-panel .pkg-foot-note { font-size: 12.5px; color: var(--desk-muted); }

/* ---- Executions: records the last run left ---- */
.exec-records {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.exec-records-say { display: flex; flex-direction: column; gap: 3px; }
.exec-records-t { font-weight: 600; color: var(--desk-heading); }
.exec-records-when { font-size: 12.5px; color: var(--desk-muted); }
.exec-records-act { display: inline-flex; gap: 8px; flex-wrap: wrap; }

/* ---- Runs tab: one row per run of this control ---- */
.desk-run-row { align-items: center; }
.desk-run-row .desk-row-act { gap: 8px; flex-wrap: wrap; }
.desk-run-status {
  font-weight: 600; text-transform: capitalize;
  color: var(--desk-muted);
}
.desk-run-status.ok { color: var(--success-ink); }
.desk-run-status.bad { color: var(--danger-ink); }
.desk-run-status.live { color: var(--brand-dark); }
.desk-run-outcome, .desk-run-exc { margin-left: 10px; }
.desk-run-exc { color: var(--danger-ink); font-weight: 600; }
/* WAS THE DESIGN CERTIFIED WHEN THIS RUN RAN? Provenance, so
   §2 makes it words — the colour is a second signal and never
   the only one. `--warn-ink` and not `--danger-ink`: an
   uncertified run is a legitimate test (ADR-155), not a
   failure, and painting it the same red as an exception would
   say the run went wrong when it went exactly as intended. */
.desk-run-cert {
  margin-left: 10px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em;
  font-size: 10.5px;
}
.desk-run-cert.run-cert-yes { color: var(--success-ink); }
.desk-run-cert.run-cert-no { color: var(--warn-ink); }
.desk-run-open {
  font: inherit; font-size: 12.5px; font-weight: 600;
  padding: 5px 11px; border-radius: var(--desk-radius-sm);
  border: 1px solid var(--desk-line-strong);
  background: var(--desk-card); color: var(--desk-heading);
  cursor: pointer;
}
.desk-run-open:hover { border-color: var(--brand); }

/* ---- button ---- */
.desk-btn {
  font: inherit; font-size: 13px; font-weight: 600;
  padding: 7px 14px; border-radius: var(--desk-radius-sm);
  border: 1px solid var(--desk-line-strong);
  background: var(--desk-card); color: var(--desk-heading);
  cursor: pointer;
}
.desk-btn:hover { background: var(--desk-tint); }
.desk-btn.primary {
  background: var(--desk-accent); border-color: var(--desk-accent);
  color: #fff;
}
.desk-btn.primary:hover { background: var(--desk-heading); }
.desk-btn:focus-visible {
  outline: 2px solid var(--desk-accent); outline-offset: 2px;
}

@media (max-width: 900px) {
  .desk-stats { grid-template-columns: repeat(2, 1fr); }
  .desk-row { flex-wrap: wrap; }
  .desk-row-due { width: auto; text-align: left; }
}

/* ---- the control subpage (detail.js) ------------------------
   Same scope, same tokens: `.desk` wraps this page too, so the
   canvas palette is declared once and the chrome is untouched. */
.desk-crumb {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--desk-muted);
}
.desk-crumb-link {
  font: inherit; font-size: 12px; padding: 0; border: 0;
  background: none; color: var(--desk-accent); cursor: pointer;
}
.desk-crumb-link:hover { text-decoration: underline; }
.desk-crumb-sep { color: var(--desk-subtle); }

.desk-dhead {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
}
/* The text block takes the slack so the actions stay on the
   right edge. Without a basis it grew to its content width and
   pushed the buttons onto their own line, which read as a
   toolbar under the title rather than as the page's action. */
.desk-dhead-text { flex: 1 1 340px; min-width: 0; }
.desk-dhead-line {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
}
.desk-dhead-id {
  font-family: var(--font-mono); font-size: 12px;
  font-weight: 600; color: var(--desk-accent);
}
.desk-badge {
  font-size: 11px; font-weight: 600; padding: 3px 9px;
  border-radius: var(--desk-pill);
  border: 1px solid var(--desk-line-strong);
  background: var(--desk-tint); color: var(--desk-body);
}
.desk-badge.danger {
  border-color: var(--desk-warn); background: var(--desk-warn-soft);
  color: var(--desk-warn);
}
.desk-dtitle {
  margin: 8px 0 5px; font-size: 26px; font-weight: 700;
  letter-spacing: -.02em; line-height: 1.15;
  color: var(--desk-heading);
}
.desk-dsub { margin: 0; font-size: 13.5px; color: var(--desk-muted); }
.desk-dhead-act { flex: none; display: inline-flex; }

.desk-dcols {
  display: grid; grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr);
  gap: 16px; align-items: start;
}
.desk-dmain, .desk-dside {
  display: flex; flex-direction: column; gap: 16px; min-width: 0;
}
.desk-block {
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  padding: 16px 18px;
}
.desk-block-title {
  margin: 0 0 9px; font-size: 12px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--desk-subtle);
}
.desk-para {
  margin: 0; font-size: 13.5px; line-height: 1.65;
  color: var(--desk-body); text-wrap: pretty;
}
.desk-absent {
  margin: 0; font-size: 13px; color: var(--desk-muted);
  font-style: italic;
}

.desk-facts { margin: 0; display: flex; flex-direction: column; gap: 8px; }
.desk-fact {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; font-size: 12.5px;
}
.desk-fact dt { color: var(--desk-muted); }
.desk-fact dd {
  margin: 0; text-align: right; font-weight: 600;
  color: var(--desk-heading);
}

.desk-inputs { display: flex; flex-direction: column; gap: 10px; }
.desk-input {
  padding: 11px 13px; border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius-sm); background: var(--desk-tint);
}
.desk-input-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px;
}
.desk-input-label {
  font-size: 13px; font-weight: 600; color: var(--desk-heading);
}
.desk-input-type {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--desk-subtle); text-transform: uppercase;
}
.desk-input-src {
  margin-top: 3px; font-size: 12px; color: var(--desk-muted);
}
.desk-input-fields {
  margin-top: 5px; font-size: 11.5px; color: var(--desk-subtle);
  text-wrap: pretty;
}

.desk-soon { display: flex; align-items: flex-start; gap: 12px; }
.desk-soon-icon {
  flex: none; display: inline-flex; color: var(--desk-subtle);
}
.desk-soon-icon svg { width: 18px; height: 18px; }
.desk-soon-title {
  font-size: 13.5px; font-weight: 700; color: var(--desk-heading);
}
.desk-soon-body {
  margin: 4px 0 0; font-size: 13px; color: var(--desk-muted);
  text-wrap: pretty;
}

/* ---- Control detail, built to the published design ----------
   Screen `Control detail — C001` in
   devdocs/design/UI/control-execution-module/. ONE row shape
   serves the Overview attribute section and every Configuration
   section, because the design draws them identically — mono key,
   sentence, mono sub-line, right-aligned tag.

   No token is added. The bundle's palette maps onto the `desk-`
   set already here; its Outfit/Figtree/Plex faces and its
   #0061FE do not port (DESIGN.md §5, §6). */
.cds-card { padding: 0; }
.cds-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 13px 18px;
  border-bottom: 1px solid var(--desk-line);
}
.cds-title {
  margin: 0; font-size: 13.5px; font-weight: 600;
  color: var(--desk-heading); text-transform: none; letter-spacing: 0;
}
.cds-note {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--desk-subtle); text-align: right;
}
.cds-rows { padding: 0 18px; }
.cds-row {
  display: flex; gap: 16px; align-items: flex-start;
  padding: 11px 0; border-bottom: 1px solid var(--desk-line);
}
.cds-row:last-child { border-bottom: 0; }
.cds-main { flex: 1 1 auto; min-width: 150px; }
.cds-k {
  font-family: var(--font-mono); font-size: 11.5px;
  font-weight: 600; color: var(--desk-accent); word-break: break-word;
}
.cds-name {
  margin-top: 3px; font-size: 13px; font-weight: 600;
  color: var(--desk-heading);
}
.cds-v {
  margin-top: 4px; font-size: 13px; line-height: 1.55;
  color: var(--desk-body); text-wrap: pretty;
}
.cds-sub {
  margin-top: 4px; font-family: var(--font-mono);
  font-size: 10.5px; color: var(--desk-subtle); text-wrap: pretty;
}
/* The tag is a WORD first. Tone only ever accompanies it — §8
   refuses colour carrying meaning on its own. */
.cds-tag {
  flex: none; text-align: right; white-space: nowrap;
  font-size: 11.5px; font-weight: 600; color: var(--desk-muted);
}
.cds-tag.ok { color: var(--success-ink); }
.cds-tag.warn { color: var(--warn-ink); }
.cds-tag.bad { color: var(--danger-ink); }
.cds-tag.info { color: var(--desk-accent); }

.cds-second { margin-top: 16px; }
.cds-accent { border-left: 3px solid var(--danger-ink); }
.cds-live {
  border-color: var(--desk-accent); background: var(--desk-accent-soft);
  color: var(--desk-accent);
}

.cds-quick { padding: 6px; }
.cds-quick-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 10px 12px; border: 0; border-radius: var(--desk-radius-sm);
  background: none; font: inherit; font-size: 13px;
  color: var(--desk-body); text-align: left; cursor: pointer;
}
.cds-quick-item:hover { background: var(--desk-tint); }
.cds-quick-icon { flex: none; display: inline-flex; color: var(--desk-muted); }
.cds-quick-icon svg { width: 15px; height: 15px; }

.cds-lede {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.cds-lede-t {
  margin: 0; flex: 1 1 340px; max-width: 640px;
  font-size: 13.5px; color: var(--desk-muted); text-wrap: pretty;
}
.cds-lede-act { flex: none; display: inline-flex; gap: 9px; }

/* The Simple / Advanced toggle on the Configuration tab
   (2026-09-17). `.facets` already carries the row's layout and
   `.facet` the button — this only places the bar inside the desk
   page, which has its own rhythm. Nothing about the buttons is
   redefined: a second definition of a facet is how the Command
   Center's lanes and this bar start to look like two controls. */
.cds-depth { margin: 0; }

/* What the Edit config button says after it opens the Copilot.
   Full width under the lede, so it never squeezes the buttons. */
.cds-lede-say {
  flex: 1 1 100%; margin: 8px 0 0;
  font-size: 12.5px; color: var(--desk-muted); text-wrap: pretty;
}

/* Which flow actually ran, when it is not the bound one. A
   sentence, not a badge: it needs two names in it. */
.lp-ranwhich {
  margin: 8px 0 0; padding: 7px 11px;
  border-left: 3px solid var(--warn-ink, var(--desk-warn));
  background: var(--desk-warn-soft, var(--desk-tint));
  font-size: 12.5px; color: var(--desk-body); text-wrap: pretty;
}

/* The steps a person still has to decide, on the packet
   (2026-09-21). A row per step, the existing decision form
   mounted under it. */
.pk-undecided { margin-top: 20px; }
.pk-und-row {
  margin: 12px 0; padding: 13px 16px;
  border: 1px solid var(--desk-line-strong);
  border-radius: var(--desk-radius); background: var(--surface);
}
.pk-und-head {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 10px;
}
.pk-und-step {
  font-family: var(--font-mono); font-size: 12px;
  font-weight: 700; color: var(--desk-accent);
}
.pk-und-stage, .pk-und-attrs {
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--desk-subtle);
}
/* A WORD carries it; the tone only ever accompanies — §8. */
.pk-und-holds {
  margin-left: auto; font-size: 11px; font-weight: 600;
  color: var(--desk-muted);
}
.pk-und-holds.on { color: var(--danger-ink); }
.pk-und-prop {
  margin: 8px 0 0; font-size: 12.5px; color: var(--desk-body);
  text-wrap: pretty;
}
.pk-und-form { margin-top: 10px; }

/* PROVISIONAL in the packet's eyebrow. A WORD carries it; the
   tone only ever accompanies — §8. */
.pk-provisional {
  margin-left: 8px; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--danger-line, var(--desk-line-strong));
  color: var(--danger-ink); background: var(--desk-tint);
  font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase;
}

/* The design lifecycle, inside the design record card
   (2026-09-17). It WRITES, so it is separated from the rows it
   acts on by a rule rather than floated among them — §2 puts the
   action near the task, not inside the evidence. */
.cds-life {
  padding: 14px 18px 16px;
  border-top: 1px solid var(--desk-line);
}
.cds-life-fields {
  display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 12px;
}
.cds-life-field { flex: 1 1 240px; min-width: 0; }
.cds-life-lbl {
  display: block; margin-bottom: 4px;
  font-family: var(--font-mono); font-size: 10.5px;
  color: var(--desk-subtle);
}
.cds-life-in {
  width: 100%; box-sizing: border-box; padding: 7px 10px;
  border: 1px solid var(--desk-line-strong);
  border-radius: var(--desk-radius);
  background: var(--surface); color: var(--desk-body);
  font-size: 13px;
}
.cds-life-act {
  display: flex; flex-wrap: wrap; align-items: center; gap: 9px;
}
.cds-life-say { font-size: 12.5px; color: var(--desk-muted); }
.cds-life-say.bad { color: var(--danger-ink); }
.cds-life-hint {
  margin: 10px 0 0; font-size: 12.5px;
  color: var(--desk-subtle); text-wrap: pretty;
}

/* View source. READ-ONLY, and it has to LOOK it — a bordered
   block of monospace that is plainly a document, not a field. */
.cds-src .cds-head { border-bottom: 0; padding-bottom: 0; }
.cds-srcnote {
  margin: 4px 18px 0; font-size: 12.5px;
  color: var(--desk-muted); text-wrap: pretty;
}
.cds-srcpre {
  margin: 12px 18px 16px; padding: 14px 16px;
  border: 1px solid var(--desk-line); border-radius: var(--desk-radius);
  background: var(--desk-tint);
  font-family: var(--font-mono); font-size: 11.5px; line-height: 1.6;
  color: var(--desk-body);
  /* A config is 350 lines. Capped and scrolled so the sections
     below stay reachable without a page-length scroll past it. */
  max-height: 60vh; overflow: auto;
  /* Long single lines scroll THIS BOX, never the page — the
     responsive rule every table and code block here follows. */
  white-space: pre; tab-size: 2;
}

.cds-notice {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 13px 16px; border-radius: var(--desk-radius);
  border: 1px solid var(--desk-line-strong);
  background: var(--desk-tint);
}
.cds-notice.info {
  border-color: var(--brand-line); background: var(--desk-accent-soft);
}
.cds-notice.warn {
  border-color: var(--warn-line); background: var(--desk-warn-soft);
}
.cds-notice-icon { flex: none; display: inline-flex; color: var(--desk-muted); }
.cds-notice.info .cds-notice-icon { color: var(--desk-accent); }
.cds-notice.warn .cds-notice-icon { color: var(--desk-warn); }
.cds-notice-icon svg { width: 16px; height: 16px; }
.cds-notice-body { flex: 1; min-width: 0; }
.cds-notice-t { font-size: 13px; font-weight: 600; color: var(--desk-heading); }
.cds-notice.info .cds-notice-t { color: var(--desk-accent); }
.cds-notice.warn .cds-notice-t { color: var(--desk-warn); }
.cds-notice-p {
  margin: 3px 0 0; font-size: 12.5px; color: var(--desk-body);
  text-wrap: pretty;
}

/* The delegate roster. Four columns, and the last is the route
   to the designer — this table never writes. */
.cds-rhead, .cds-rrow {
  display: flex; align-items: center; gap: 12px; padding: 12px 18px;
  border-bottom: 1px solid var(--desk-line);
}
.cds-rhead {
  background: var(--desk-tint); padding: 10px 18px;
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--desk-subtle);
}
.cds-rrow:last-child { border-bottom: 0; }
.cds-rc-person { flex: 1; min-width: 0; }
.cds-rc-role { flex: none; width: 146px; font-size: 12.5px; color: var(--desk-muted); }
.cds-rc-scope { flex: none; width: 210px; font-family: var(--font-mono); font-size: 12px; color: var(--desk-body); word-break: break-word; }
.cds-rc-act { flex: none; width: 84px; text-align: right; }
.cds-rperson { font-size: 13.5px; font-weight: 600; color: var(--desk-heading); }
.cds-rperson.gap { color: var(--desk-warn); }
.cds-rnote { margin-top: 2px; font-family: var(--font-mono); font-size: 10.5px; color: var(--desk-subtle); }
.cds-rbtn {
  font: inherit; font-size: 12px; padding: 4px 10px;
  border: 1px solid var(--desk-line-strong);
  border-radius: var(--desk-radius-sm);
  background: var(--desk-card); color: var(--desk-body); cursor: pointer;
}
.cds-rbtn:hover { background: var(--desk-tint); }

@media (max-width: 900px) {
  .desk-dcols { grid-template-columns: 1fr; }
  /* The roster stacks rather than scrolls: three of its four
     columns are short strings, and a horizontal scroller on a
     phone hides the scope, which is the column that matters.

     **The person cell takes a WHOLE row.** Left to wrap on its
     own it kept its narrow column and pushed its sub-line into
     three wrapped lines beside the role, which read as a broken
     table rather than as a stacked one — seen at 620px, not
     predicted. Forcing the basis to 100% puts role, scope and
     the action on their own line underneath. */
  .cds-rhead { display: none; }
  .cds-rrow { flex-wrap: wrap; row-gap: 6px; }
  .cds-rc-person { flex: 1 1 100%; }
  .cds-rc-role, .cds-rc-scope { width: auto; }
  .cds-rc-act { flex: 1 1 auto; }
}

/* ---- Executions: the pre-run surface (executions.js) --------
   Layout from the wireframe canvas's `Control execution — C001`
   artboard. Same `.desk` scope and the same tokens, so the
   chrome is untouched. */
.exec-pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px; font-weight: 600; padding: 3px 10px;
  border-radius: var(--desk-pill);
  border: 1px dashed var(--desk-line-strong);
  background: var(--desk-card); color: var(--desk-muted);
}

.exec-meta {
  display: grid; grid-template-columns: repeat(5, 1fr);
  background: var(--desk-card); border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius); box-shadow: var(--desk-shadow);
  overflow: hidden;
}
.exec-meta-cell {
  padding: 13px 16px; border-right: 1px solid var(--desk-line);
  min-width: 0;
}
.exec-meta-cell:last-child { border-right: 0; }
.exec-meta-k {
  font-size: 10.5px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--desk-subtle);
}
.exec-meta-v {
  margin-top: 5px; font-family: var(--font-mono); font-size: 13px;
  color: var(--desk-heading); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap;
}

.exec-block-head { margin-bottom: 14px; }
.exec-block-title {
  margin: 0; font-size: 16px; font-weight: 700;
  letter-spacing: -.01em; color: var(--desk-heading);
}
.exec-block-sub {
  margin: 3px 0 0; font-size: 12.5px; color: var(--desk-muted);
}

.exec-progress-row {
  display: flex; align-items: baseline;
  justify-content: space-between; gap: 12px; margin-bottom: 6px;
}
.exec-progress-label {
  font-size: 13px; font-weight: 600; color: var(--desk-heading);
}
.exec-progress-pct {
  font-family: var(--font-mono); font-size: 12px;
  color: var(--desk-muted);
}
.exec-progress {
  height: 6px; border-radius: 4px; background: var(--desk-line);
  overflow: hidden; margin-bottom: 16px;
}
.exec-progress > span {
  display: block; height: 6px; width: 0;
  background: var(--desk-accent);
}

.exec-stages { display: flex; gap: 8px; }
.exec-stage {
  flex: 1 1 0; min-width: 0; padding: 10px 12px;
  border: 1px solid var(--desk-line);
  border-radius: var(--desk-radius-sm); background: var(--desk-tint);
  display: flex; flex-direction: column; gap: 6px;
}
.exec-stage-top { display: flex; align-items: center; gap: 7px; }
.exec-stage-n {
  flex: none; width: 18px; height: 18px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 10px; font-weight: 700;
  background: var(--desk-card); color: var(--desk-muted);
  border: 1px solid var(--desk-line-strong);
}
.exec-stage-label {
  font-size: 12.5px; font-weight: 600; color: var(--desk-heading);
}
.exec-stage-sub {
  font-size: 11px; color: var(--desk-muted); text-wrap: pretty;
}
.exec-stage-dur {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--desk-subtle);
}

.exec-empty { text-align: center; padding: 40px 20px; }
.exec-empty-icon {
  display: inline-flex; color: var(--desk-line-strong);
}
.exec-empty-icon svg { width: 28px; height: 28px; }
.exec-empty-title {
  margin-top: 10px; font-size: 17px; font-weight: 700;
  color: var(--desk-heading);
}
.exec-empty-body {
  margin: 8px auto 0; max-width: 46ch; font-size: 13.5px;
  color: var(--desk-muted); text-wrap: pretty;
}

@media (max-width: 1100px) {
  .exec-meta { grid-template-columns: repeat(2, 1fr); }
  .exec-meta-cell { border-bottom: 1px solid var(--desk-line); }
  .exec-stages { flex-wrap: wrap; }
  .exec-stage { flex: 1 1 140px; }
}

/* ---- Executions: live stage states -------------------------- */
.exec-stage.running {
  border-color: var(--desk-accent);
  background: var(--desk-accent-soft);
}
.exec-stage.running .exec-stage-n {
  background: var(--desk-accent); color: #fff;
  border-color: var(--desk-accent);
}
.exec-stage.running .exec-stage-dur {
  color: var(--desk-accent); font-weight: 600;
}
.exec-stage.done { background: var(--desk-card); }
.exec-stage.done .exec-stage-n {
  background: var(--desk-heading); color: #fff;
  border-color: var(--desk-heading);
}
.exec-stage.failed {
  border-color: var(--desk-warn); background: var(--desk-warn-soft);
}
.exec-stage.failed .exec-stage-n,
.exec-stage.failed .exec-stage-dur {
  color: var(--desk-warn); font-weight: 600;
}
.exec-progress > span { transition: width .35s ease; }

/* A stopped stage is NEUTRAL, never the warn colour. Somebody
   halted it on purpose; painting it like a failure is how a
   non-event gets investigated. */
.exec-stage.stopped { background: var(--desk-tint); }
.exec-stage.stopped .exec-stage-n {
  background: var(--desk-line); color: var(--desk-muted);
}
.exec-stage.stopped .exec-stage-dur {
  color: var(--desk-muted); font-weight: 600;
}

/* ---- Executions: the drop's own header row ------------------ */
.exec-drop-head {
  display: flex; align-items: flex-start;
  justify-content: space-between; gap: 16px;
}

/* ==================== the command palette ====================
   One box for going somewhere, finding a record, and asking the
   Copilot. Sits at 1300 — above the modal ladder's top (1200),
   because Cmd-K is reachable from inside any of those surfaces
   and a palette that opened behind the thing you opened it from
   is worse than one that does not open.

   No new tokens. Every colour here is one DESIGN.md §5 already
   defines, which is also what keeps design_tokens_test.py from
   having an opinion about this block. */
.pal-scrim {
  position: fixed; inset: 0; z-index: 1300;
  background: rgba(15, 23, 42, .38);
  display: flex; justify-content: center;
  /* NOT centred vertically. The list grows downward, and a box
     centred on the viewport walks up the screen as it fills —
     the first row moves while the reader is looking at it. */
  align-items: flex-start; padding: 12vh 16px 16px;
}
.pal {
  width: 100%; max-width: 560px;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-overlay);
  overflow: hidden;
}
.pal-input {
  width: 100%; box-sizing: border-box;
  height: 52px; padding: 0 16px;
  border: 0; border-bottom: 1px solid var(--line);
  background: none; color: var(--text-primary);
  font-size: 15px;
}
.pal-input::placeholder { color: var(--text-muted); }
/* The dialog itself carries the visible boundary, so a ring on
   the input inside it draws a second box around the same thing. */
.pal-input:focus-visible { outline: none; }
.pal-list {
  margin: 0; padding: 4px; list-style: none;
  max-height: 46vh; overflow-y: auto;
}
.pal-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 9px 12px; border-radius: var(--radius);
  cursor: pointer;
}
/* Selection is the SAME state for the pointer and the keyboard —
   one class, set by both, so the row under the cursor and the row
   Enter will open are never two different rows. */
.pal-row.sel { background: var(--brand-tint); }
.pal-row-label {
  flex: 1; min-width: 0; color: var(--text-primary);
  font-size: 13.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pal-row.sel .pal-row-label { color: var(--brand-dark); }
.pal-row-sub {
  flex: none; color: var(--text-secondary); font-size: 11px;
  text-transform: uppercase; letter-spacing: .04em;
}
.pal-empty {
  padding: 14px 12px; color: var(--text-secondary);
  font-size: 13px;
}
.pal-hint {
  margin: 0; padding: 8px 16px; font-size: 11px;
  color: var(--text-muted);
  border-top: 1px solid var(--line);
  background: var(--surface-subtle);
}
@media (max-width: 719px) {
  /* At mobile widths the keyboard takes the bottom half, so the
     box starts higher and the list gets what is left. */
  .pal-scrim { padding: 6vh 10px 10px; }
  .pal-list { max-height: 38vh; }
}
@media (prefers-reduced-motion: reduce) {
  .pal-row { transition: none; }
}

/* ================================================================
   THE EXECUTION LOG AS A PAGE — views/run_log_page.js
   Built to the published design (artifact 45aa13cb). Structure,
   hierarchy, scale and tracking are the design's; the FACES and
   the #0061FE family are not portable — DESIGN.md §6 makes system
   UI typography mandatory and §5 marks that blue brand-expression
   only. Every colour below is a token that already existed.
   ================================================================ */
.lp-page {
  display: flex; flex-direction: column; gap: 16px;
  max-width: 1180px;
}
.lp-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; overflow: hidden;
  box-shadow: 0 1px 3px rgba(15, 23, 42, .08),
              0 1px 2px rgba(15, 23, 42, .04);
}

/* ---- header ---- */
.lp-hrow { display: flex; gap: 20px; flex-wrap: wrap; }
.lp-hmain { min-width: 0; flex: 1; }
.lp-badges {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.lp-idchip {
  font-family: var(--font-mono); font-size: 13px; font-weight: 600;
  color: var(--brand-dark); background: var(--brand-tint);
  padding: 3px 8px; border-radius: 6px;
}
.lp-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: 999px; font-size: 11.5px;
  font-weight: 600; background: var(--success-tint);
  color: var(--success-ink);
}
.lp-pill-bad {
  background: var(--danger-tint); color: var(--danger-ink);
}
.lp-dot {
  width: 7px; height: 7px; border-radius: 999px;
  background: currentColor;
}
.lp-badge {
  font-size: 11px; font-weight: 600; padding: 3px 9px;
  border-radius: 999px; background: var(--line);
  color: var(--text-secondary);
}
.lp-badge-bad {
  background: var(--danger-tint); color: var(--danger-ink);
}
.lp-badge-ok {
  background: var(--success-tint); color: var(--success-ink);
}
.lp-topline-live { border-top-color: var(--warn-ink); }
.lp-eyeb-live { color: var(--warn-ink); }
.pk-mk-warn { background: var(--warn-tint); color: var(--warn-ink); }
.pk-amber { border-top: 3px solid var(--warn-ink); }
.lp-h1 {
  font-size: 28px; font-weight: 700; letter-spacing: -.02em;
  line-height: 1.1; color: var(--text-primary); margin: 9px 0 5px;
}
.lp-lede {
  margin: 0; max-width: 760px; font-size: 13.5px; line-height: 1.6;
  color: var(--text-secondary);
}
.lp-hacts { display: flex; gap: 9px; flex: none; align-items: center; }
.lp-btn {
  font-family: inherit; font-size: 13px; font-weight: 600;
  padding: 8px 14px; border-radius: 6px;
  border: 1px solid var(--line-strong); background: var(--surface);
  color: var(--text-secondary); cursor: pointer;
  text-decoration: none; display: inline-block;
}
.lp-btn:hover { border-color: var(--brand); color: var(--brand); }
.lp-btn-pri {
  background: var(--brand); border-color: var(--brand); color: #fff;
}
.lp-btn-pri:hover { background: var(--brand-dark); color: #fff; }

/* ---- pipeline strip ---- */
.lp-pipe { display: flex; flex-wrap: wrap; }
.lp-stage {
  flex: 1 1 132px; padding: 13px 15px;
  border-left: 3px solid var(--success-ink);
  border-bottom: 1px solid var(--line);
}
.lp-bad { border-left-color: var(--danger-ink); }
/* A stage the run REACHED whose audit record is empty. Not the
   danger border — it ran — but not a plain cell either, or a
   stdout caption reads as a pass. */
.lp-stage-unrecorded { border-left-color: var(--warn-ink); }
.lp-unrec {
  display: block; margin-top: 3px; font-size: 11px;
  font-style: normal; font-weight: 600; color: var(--warn-ink);
}
.lp-stage b {
  display: block; font-size: 12.5px; font-weight: 700;
  color: var(--text-primary);
}
.lp-stage span {
  display: block; font-size: 11.5px; color: var(--text-secondary);
  margin-top: 2px; overflow-wrap: anywhere;
}
.lp-stage em {
  display: block; font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted); margin-top: 3px; font-style: normal;
}

/* ---- card head, shared ---- */
.lp-chead {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 20px; border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.lp-ct {
  font-size: 13.5px; font-weight: 600; color: var(--text-primary);
}
.lp-spacer { flex: 1; min-width: 0; }
.lp-lines {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-secondary);
}
.lp-said {
  padding: 14px 20px; font-size: 13.5px; line-height: 1.6;
  color: var(--text-primary);
}

/* ---- run-ended card ---- */
.lp-topline { border-top: 3px solid var(--success-ink); }
.lp-topline-bad { border-top-color: var(--danger-ink); }
.lp-eyeb {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--success-ink);
}
.lp-eyeb-bad { color: var(--danger-ink); }
.lp-now {
  flex: 1; min-width: 0; font-size: 20px; font-weight: 700;
  letter-spacing: -.01em; color: var(--text-primary);
}

/* ---- live log console ---- */
.lp-console {
  max-height: 320px; overflow-y: auto; padding: 12px 18px 14px;
  display: flex; flex-direction: column; gap: 6px;
  background: var(--surface-subtle);
}
.lp-lrow { display: flex; gap: 12px; align-items: flex-start; }
.lp-lt {
  flex: none; font-family: var(--font-mono); font-size: 11px;
  line-height: 1.6; color: var(--text-muted);
}
.lp-lg {
  flex: none; width: 132px; white-space: nowrap;
  font-family: var(--font-mono); font-size: 11px; line-height: 1.6;
  font-weight: 700; color: var(--brand-dark);
  overflow: hidden; text-overflow: ellipsis;
}
.lp-lg-sys { color: var(--text-muted); font-weight: 500; }
.lp-lg-bad { color: var(--danger-ink); }
.lp-lx {
  flex: 1; min-width: 0; font-family: var(--font-mono);
  font-size: 11px; line-height: 1.6; color: var(--text-primary);
  word-break: break-word;
}
.lp-la {
  flex: none; width: 96px; font-family: var(--font-mono);
  font-size: 11px; line-height: 1.6; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---- event log, the three-column grid ---- */
.lp-ev { border-bottom: 1px solid var(--line); }
.lp-ev-bad { background: var(--danger-tint); }
.lp-egrid {
  display: grid; align-items: start;
  grid-template-columns: minmax(0, 168px) minmax(0, 1fr)
                         minmax(0, 176px);
}
.lp-lane {
  padding: 14px 16px; border-left: 3px solid var(--brand);
}
.lp-lane-system { border-left-color: var(--line-strong); }
.lp-lane-human { border-left-color: var(--success-ink); }
.lp-lane-bad { border-left-color: var(--danger-ink); }
.lp-k {
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand-dark);
}
.lp-lane-system .lp-k, .lp-lane-bad .lp-k {
  color: var(--text-secondary);
}
.lp-who {
  margin-top: 3px; font-size: 11.5px; color: var(--text-secondary);
  word-break: break-word;
}
.lp-ts {
  margin-top: 4px; font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted);
}
.lp-etext { padding: 14px 18px; }
.lp-b { font-size: 14px; line-height: 1.6; color: var(--text-primary); }
.lp-chips {
  margin-top: 9px; display: flex; gap: 6px; flex-wrap: wrap;
  align-items: center;
}
.lp-chip {
  font-size: 11px; font-weight: 600; padding: 3px 8px;
  border-radius: 6px; background: var(--line);
  color: var(--text-secondary);
}
.lp-chip-attr {
  font-family: var(--font-mono); background: var(--brand-tint);
  color: var(--brand-dark);
}
.lp-chip-ok {
  background: var(--success-tint); color: var(--success-ink);
}
.lp-chip-warn {
  background: var(--warn-tint); color: var(--warn-ink);
}
.lp-chip-bad {
  background: var(--danger-tint); color: var(--danger-ink);
}
.lp-emeta { padding: 14px 18px; text-align: right; }
.lp-m {
  font-family: var(--font-mono); font-size: 11.5px;
  color: var(--text-secondary);
}
.lp-ms {
  margin-top: 2px; font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted);
}
.lp-tbtn {
  margin-top: 7px; font-family: inherit; font-size: 11.5px;
  font-weight: 600; color: var(--brand); background: none;
  border: 0; padding: 0; cursor: pointer;
}
.lp-trail {
  margin: 0 18px 16px 171px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--surface-subtle);
  padding: 13px 15px; display: flex; flex-direction: column;
  gap: 3px; overflow-x: auto;
}
.lp-trail span {
  font-family: var(--font-mono); font-size: 11.5px;
  line-height: 1.7; color: var(--text-secondary);
  word-break: break-word; white-space: pre-wrap;
}
.lp-trail .lp-e { color: var(--danger-ink); }
.lp-trail .lp-g { color: var(--success-ink); }

/* ---- the gap callout ---- */
.lp-gap {
  border: 1px solid var(--danger-ink); background: var(--danger-tint);
  border-radius: 14px; padding: 16px 20px;
}
.lp-gap h2 {
  margin: 0 0 7px; font-size: 15px; font-weight: 700;
  color: var(--danger-ink);
}
.lp-gap p {
  margin: 0; font-size: 13px; line-height: 1.6; max-width: 92ch;
  color: var(--text-primary);
}

/* ---- stage accordion ---- */
.lp-stack { display: flex; flex-direction: column; gap: 10px; }
.lp-sh {
  display: flex; align-items: center; gap: 14px; padding: 14px 18px;
  cursor: pointer; width: 100%; background: none; border: 0;
  text-align: left; font-family: inherit;
}
.lp-n {
  flex: none; width: 24px; height: 24px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  background: var(--success-tint); color: var(--success-ink);
}
.lp-n-bad {
  background: var(--danger-tint); color: var(--danger-ink);
}
.lp-lb {
  flex: none; width: 152px; font-size: 14px; font-weight: 600;
  color: var(--text-primary);
}
/* An attribute id is a mono identifier, not a stage name, and
   several are longer than the 152px a stage needs. */
.lp-lb-wide {
  width: 210px; font-family: var(--font-mono); font-size: 12.5px;
  overflow-wrap: anywhere;
}

/* The grouping control — screen 01B's segmented pair: a sunken
   track with the active segment raised and white. An ADDITION to
   the design, in the design's own idiom. */
.lp-seg {
  flex: none; display: inline-flex; gap: 4px; padding: 3px;
  background: var(--line); border-radius: 7px;
}
.lp-segb {
  font: inherit; font-size: 11.5px; font-weight: 600;
  padding: 5px 11px; border: 0; border-radius: 5px;
  background: none; color: var(--text-secondary); cursor: pointer;
}
.lp-segb.on {
  background: var(--surface); color: var(--text-primary);
  box-shadow: 0 1px 2px rgba(15, 27, 51, .06);
}
.lp-segb:hover:not(.on) { color: var(--text-primary); }
.lp-mode[hidden] { display: none !important; }
.lp-stack.lp-mode { padding: 12px; }
.lp-mid {
  flex: 1; min-width: 0; font-size: 12.5px; line-height: 1.5;
  color: var(--text-secondary); overflow-wrap: anywhere;
}
.lp-pl {
  flex: none; padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600;
  background: var(--success-tint); color: var(--success-ink);
}
.lp-pl-bad {
  background: var(--danger-tint); color: var(--danger-ink);
}
/* The pill's base is green, so every state that is NOT met needs
   its own class — `qualified` and `gap` inheriting the success
   tint is how a gap came to read as met. */
.lp-pl-warn {
  background: var(--warn-tint); color: var(--warn-ink);
}
.lp-pl-mute {
  background: var(--surface-subtle); color: var(--text-secondary);
}
/* The count badge is a COUNT, not a verdict: how many events
   named this attribute, which routinely differs from whether any
   of them concluded. */
.lp-n-mute {
  background: var(--surface-subtle); color: var(--text-secondary);
}
.lp-sbody {
  border-top: 1px solid var(--line); background: var(--surface-subtle);
  padding: 14px 18px 16px; display: flex; flex-direction: column;
  gap: 6px; overflow-x: auto;
}
.lp-sline { display: flex; gap: 12px; align-items: flex-start; }
.lp-st {
  flex: none; width: 60px; font-family: var(--font-mono);
  font-size: 11px; line-height: 1.6; color: var(--text-muted);
}
.lp-sid {
  flex: none; width: 150px; font-family: var(--font-mono);
  font-size: 11px; line-height: 1.6; font-weight: 600;
  color: var(--brand-dark); overflow: hidden;
  text-overflow: ellipsis;
}
.lp-sx {
  flex: 1; min-width: 0; font-size: 12.5px; line-height: 1.6;
  color: var(--text-primary);
}
.lp-sat {
  flex: none; width: 180px; font-family: var(--font-mono);
  font-size: 11px; line-height: 1.7; color: var(--text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

@media (max-width: 1000px) {
  .lp-egrid { grid-template-columns: minmax(0, 1fr); }
  .lp-lane {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 4px 10px; padding-bottom: 0;
  }
  .lp-who, .lp-ts { margin-top: 0; }
  .lp-emeta {
    text-align: left; padding-top: 0; display: flex;
    flex-wrap: wrap; align-items: center; gap: 6px 12px;
  }
  .lp-trail { margin-left: 19px; }
  .lp-sh { flex-wrap: wrap; }
  .lp-lb { width: auto; }
  .lp-mid { flex-basis: 100%; order: 5; }
  .lp-la, .lp-sat { display: none; }
}
@media (max-width: 620px) {
  .lp-h1 { font-size: 23px; }
  .lp-lg { width: 92px; }
}

/* ================================================================
   THE EVIDENCE PACKET AS A PAGE — views/packet_page.js
   Built to the published design (artifact eeb83dc7). Same rule as
   the log page above: structure and hierarchy are the design's,
   the faces and the #0061FE family are not portable, every colour
   is a token that already existed.
   ================================================================ */
.pk-page {
  display: flex; flex-direction: column; gap: 16px;
  max-width: 1180px;
}
.pk-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 14px; overflow: hidden;
  box-shadow: 0 1px 3px rgba(15, 23, 42, .08),
              0 1px 2px rgba(15, 23, 42, .04);
}
.pk-hrow { display: flex; gap: 20px; flex-wrap: wrap; }
.pk-hmain { flex: 1; min-width: 0; }
.pk-eyebrow {
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-muted);
}
.pk-h1 {
  margin: 6px 0 5px; font-size: 26px; font-weight: 700;
  letter-spacing: -.02em; color: var(--text-primary);
}
.pk-lede {
  margin: 0; max-width: 760px; font-size: 13.5px; line-height: 1.6;
  color: var(--text-secondary);
}
.pk-hacts { display: flex; gap: 9px; flex: none; align-items: center; }
.pk-btn {
  font-family: inherit; font-size: 13px; font-weight: 600;
  padding: 8px 14px; border-radius: 6px;
  border: 1px solid var(--line-strong); background: var(--surface);
  color: var(--text-secondary); cursor: pointer;
}
.pk-btn:hover { border-color: var(--brand); color: var(--brand); }

/* ---- stat strip ---- */
.pk-stats {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: 14px; overflow: hidden;
}
.pk-st {
  padding: 14px 16px; background: var(--surface);
  display: flex; flex-direction: column; gap: 6px;
}
.pk-v {
  font-size: 26px; font-weight: 800; letter-spacing: -.03em;
  line-height: 1; color: var(--text-primary);
}
.pk-v-ok { color: var(--success-ink); }
.pk-v-warn { color: var(--warn-ink); }
.pk-v-bad { color: var(--danger-ink); }
.pk-l { font-size: 12.5px; font-weight: 600; color: var(--text-primary); }
.pk-n { font-size: 11.5px; line-height: 1.5; color: var(--text-secondary); }
.pk-cs { display: flex; flex-wrap: wrap; gap: 4px; }
.pk-cc {
  display: inline-flex; align-items: center; padding: 2px 6px;
  border-radius: 999px; font-family: var(--font-mono);
  font-size: 10px; font-weight: 700; white-space: nowrap;
  background: var(--line); color: var(--text-secondary);
}
.pk-cc-ok { background: var(--success-tint); color: var(--success-ink); }
.pk-cc-warn { background: var(--warn-tint); color: var(--warn-ink); }
.pk-cc-bad { background: var(--danger-tint); color: var(--danger-ink); }

/* ---- warn box ---- */
.pk-warn {
  border: 1px solid var(--danger-ink); background: var(--danger-tint);
  border-radius: 14px; padding: 16px 20px;
}
.pk-warn h2 {
  margin: 0 0 7px; font-size: 15px; font-weight: 700;
  color: var(--danger-ink);
}
.pk-warn p {
  margin: 0; font-size: 13px; line-height: 1.6; max-width: 94ch;
  color: var(--text-primary);
}

/* ---- card head, shared ---- */
.pk-chead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.pk-t { font-size: 13.5px; font-weight: 600; color: var(--text-primary); }
.pk-s { margin-top: 3px; font-size: 12px; color: var(--text-secondary); }
.pk-r {
  font-family: var(--font-mono); font-size: 11px;
  color: var(--text-muted);
}
.pk-said { padding: 14px 18px; font-size: 13px; color: var(--text-secondary); }

/* ---- rules grid ---- */
.pk-rules {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px; background: var(--line);
}
.pk-rule { padding: 14px 18px; background: var(--surface); }
.pk-k {
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-muted);
}
.pk-rule p {
  margin: 7px 0 0; font-size: 13px; line-height: 1.6;
  color: var(--text-primary);
}

/* ---- section bar ---- */
.pk-secbar {
  display: flex; align-items: center; gap: 6px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 999px; overflow-x: auto; position: sticky; top: 0;
  z-index: 20;
  box-shadow: 0 1px 3px rgba(15, 23, 42, .08);
}
/* A BUTTON, not a link — it scrolls this page and must never
   reach the hash router. The resets below are what a <button>
   needs to keep the pill look an <a> got for free. */
.pk-seclink {
  flex: none; display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 13px; border-radius: 999px; font-size: 12.5px;
  font-weight: 600; white-space: nowrap; background: var(--line);
  color: var(--text-secondary); text-decoration: none;
  border: 0; font-family: inherit; cursor: pointer;
}
.pk-seclink:hover { background: var(--brand-tint); color: var(--brand-dark); }
.pk-hot { background: var(--danger-tint); color: var(--danger-ink); }
.pk-nn { font-family: var(--font-mono); font-size: 10.5px; opacity: .75; }

/* ---- execution contract ---- */
.pk-contract {
  display: grid; gap: 1px; background: var(--line);
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.pk-cx { padding: 13px 16px; background: var(--surface); }
.pk-cv {
  margin-top: 5px; font-family: var(--font-mono); font-size: 12.5px;
  color: var(--text-primary); word-break: break-word;
}

/* ---- attribute table ---- */
.pk-scroll { overflow-x: auto; }
.pk-minw { min-width: 1020px; }
.pk-ahead, .pk-arow {
  display: grid; gap: 10px; padding: 11px 18px;
  grid-template-columns: 104px 104px minmax(230px, 1fr)
                         minmax(200px, 1fr) 96px 108px;
}
.pk-ahead {
  background: var(--surface-subtle); border-bottom: 1px solid var(--line);
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; color: var(--text-muted);
}
.pk-arow {
  align-items: start; border-bottom: 1px solid var(--line);
  font-size: 11.5px; line-height: 1.5;
}
.pk-gap { background: var(--danger-tint); }
.pk-aid {
  font-family: var(--font-mono); font-size: 11px; font-weight: 600;
  color: var(--brand-dark);
}
.pk-cat { color: var(--text-secondary); }
.pk-aa {
  display: block; font-size: 12.5px; font-weight: 600;
  color: var(--text-primary); line-height: 1.5;
}
.pk-at { display: block; margin-top: 3px; color: var(--text-secondary); }
.pk-obs { color: var(--text-secondary); }
.pk-obs-bad { color: var(--danger-ink); }
/* How much evidence stands behind a verdict, under the reason. */
.pk-back {
  display: block; margin-top: 3px; font-size: 11px;
  color: var(--text-muted);
}
.pk-back-no { color: var(--warn-ink); }
.pk-by { font-family: var(--font-mono); font-size: 11px; color: var(--text-muted); }
.pk-pill {
  display: inline-flex; align-items: center; padding: 2px 8px;
  border-radius: 999px; font-size: 10.5px; font-weight: 700;
  white-space: nowrap; background: var(--line);
  color: var(--text-secondary);
}
.pk-pill-ok { background: var(--success-tint); color: var(--success-ink); }
.pk-pill-warn { background: var(--warn-tint); color: var(--warn-ink); }
.pk-pill-bad { background: var(--danger-tint); color: var(--danger-ink); }

/* ---- artifacts ---- */
.pk-arthead, .pk-artrow {
  display: flex; align-items: flex-start; gap: 12px; padding: 11px 18px;
  border-bottom: 1px solid var(--line);
}
.pk-arthead {
  background: var(--surface-subtle); font-size: 10.5px; font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-muted);
}
.pk-eid {
  flex: none; width: 104px; font-family: var(--font-mono);
  font-size: 11px; font-weight: 600; color: var(--brand-dark);
}
.pk-amid { flex: 1; min-width: 0; }
.pk-pth {
  font-family: var(--font-mono); font-size: 12px;
  color: var(--text-primary); word-break: break-word;
}
.pk-asub {
  margin-top: 4px; display: flex; gap: 9px; flex-wrap: wrap;
  align-items: center; font-size: 11.5px; color: var(--text-secondary);
}
.pk-by-b {
  font-size: 11px; font-weight: 600; padding: 2px 7px;
  border-radius: 6px; background: var(--brand-tint);
  color: var(--brand-dark);
}
.pk-sha {
  flex: none; width: 104px; font-family: var(--font-mono);
  font-size: 11px; color: var(--text-secondary);
}
.pk-stt {
  flex: none; width: 74px; text-align: right; font-size: 11.5px;
  font-weight: 600; color: var(--success-ink);
}
.pk-stt-no { color: var(--danger-ink); }
/* The file is there and the record is not — neither green nor red. */
.pk-stt-part { color: var(--warn-ink); }
.pk-owed { color: var(--warn-ink); font-weight: 600; }

/* ---- seal checklist ---- */
.pk-chks { padding: 4px 18px 10px; }
.pk-chk {
  display: flex; gap: 11px; align-items: flex-start; padding: 11px 0;
  border-bottom: 1px solid var(--line);
}
.pk-chk:last-child { border-bottom: 0; }
.pk-mk {
  flex: none; width: 18px; height: 18px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; background: var(--success-tint);
  color: var(--success-ink);
}
.pk-mk-bad { background: var(--danger-tint); color: var(--danger-ink); }
.pk-cb { flex: 1; min-width: 0; }
.pk-cx-x { font-size: 13.5px; line-height: 1.55; color: var(--text-primary); }
.pk-cn {
  margin-top: 3px; font-family: var(--font-mono); font-size: 11.5px;
  line-height: 1.5; color: var(--text-secondary);
}
.pk-cn-bad { color: var(--danger-ink); }
.pk-spill {
  display: inline-flex; align-items: center; gap: 7px; padding: 5px 11px;
  border-radius: 999px; background: var(--success-tint);
  color: var(--success-ink); font-size: 12px; font-weight: 600;
}
.pk-spill-bad { background: var(--danger-tint); color: var(--danger-ink); }
.pk-dot {
  width: 7px; height: 7px; border-radius: 999px; background: currentColor;
}
.pk-sealfoot {
  display: flex; align-items: center; gap: 20px; padding: 15px 18px;
  background: var(--surface-subtle); border-top: 1px solid var(--line);
  flex-wrap: wrap;
}
.pk-sealfoot span {
  flex: 1; min-width: 260px; font-size: 12.5px; line-height: 1.6;
  color: var(--text-secondary);
}

@media (max-width: 980px) {
  .pk-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pk-rules { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 620px) {
  .pk-h1 { font-size: 22px; }
  .pk-stats { grid-template-columns: minmax(0, 1fr); }
}

/* ---- the exception queue's picker (ADR-159) ----------------
 * Single, group and all. No shared selection component exists;
 * `views/scoping.js` hand-rolls its own for the same reason,
 * and building one from two examples would be inventing a
 * contract. These are the four classes that needs.
 *
 * Every colour here is an EXISTING token. The page carries no
 * palette of its own. */
.pick {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-secondary);
  cursor: pointer;
}
/* Asymmetric on purpose: the checkbox selects the table BELOW
 * it, and an even gap left the reader guessing which of the two
 * tables it belonged to. */
.group-pick { margin: 26px 0 4px; }
.bulkbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin: 10px 0;
  padding: 10px 14px;
  background: var(--brand-tint);
  border: 1px solid var(--brand-line);
  border-radius: var(--radius);
}
.bulkbar-n { font-weight: 600; color: var(--text-primary); }
.bulkbar-note { flex: 1; min-width: 220px; color: var(--text-secondary); }
