/* helm-console — Home Screen shell styles (T1.1). T1.8 extends.
 *
 * THEME-AWARE (2026-08-27). Two layers, matching the Bridge's own mechanism
 * so the two surfaces agree on the same theme:
 *
 *   Layer 1 (this file): a complete DARK token set on bare `:root`, and a
 *     complete LIGHT set under `:root[data-theme="light"]`. Mode alone
 *     therefore renders correctly with zero JS — which is what the pre-paint
 *     snippet in index.html relies on.
 *   Layer 2 (appearance.ts): the active theme's tokens written as INLINE
 *     custom properties on <html>. Inline beats both blocks, so a theme's
 *     colors win; anything a theme does not define falls through to the mode
 *     block here.
 *
 * RULE: no rule below may name a bare hex. Every color goes through a token,
 * or the light mode silently keeps a dark value — which is exactly the class
 * of bug this file was rewritten to remove. `dark = no attribute` is the
 * default state, matching data-theme on the Bridge. */

:root {
  /* chrome — overwritten inline by the active theme */
  --hc-bg: #101418;
  /* shell chrome (theme-truth): one full ramp step DARKER than --hc-bg —
   * the Helm's --bg-deep seat (.tabstrip/.rail/.trailbar ride it); also
   * the grid's odd-row stripe. Overwritten per theme (theme.ui.bgDeep). */
  --hc-bg-deep: #0b0e12;
  --hc-fg: #d5dbe0;
  --hc-accent: #4cc38a;
  --hc-muted: #7d8590;
  --hc-line: #2a3138;
  --hc-surface: #181e24;   /* inputs, selects */
  --hc-surface2: #1d2530;  /* buttons */
  --hc-panel: #181e24;     /* dialogs, menus, cards — raised chrome sits
                              ABOVE the ground (theme-truth ordering:
                              bg-deep < bg < surface < surface-2) */
  --hc-track: #232a32;     /* progress track */
  --hc-scrim: rgba(0, 0, 0, 0.55);

  /* grid scroll rails (owner item 67) — the shared pi-stack layer
   * (pi-stack/src/client/scroll-rails.ts) draws the transcript minimap's
   * rail treatment over every AG grid and reads its colors through these
   * two seats; this mapping is the console's ONE hookup (the Helm falls
   * through to its own --ink/--accent). Theme swaps ride along because
   * --hc-fg/--hc-accent are overwritten inline by the active theme on this
   * same root. */
  --sb-rail-ink: var(--hc-fg);
  --sb-rail-accent: var(--hc-accent);

  /* semantic pill ladder — nine palettes shared by status/type/severity.
   * Named by role, not by hue's source status, because one palette serves
   * several statuses (see the rules further down). */
  --hc-p-blue-bg: #2b3a55;      --hc-p-blue-fg: #9ec1ff;
  --hc-p-cyan-bg: #263a4d;      --hc-p-cyan-fg: #7fc4e8;
  --hc-p-green-bg: #1f3d2e;     --hc-p-green-fg: #6fdca4;
  --hc-p-olive-bg: #34402a;     --hc-p-olive-fg: #b7dc8a;
  --hc-p-amber-bg: #3d3524;     --hc-p-amber-fg: #e8c47f;
  --hc-p-red-bg: #45262a;       --hc-p-red-fg: #ef9ba4;
  --hc-p-violet-bg: #3a2e4a;    --hc-p-violet-fg: #c9a8f0;
  --hc-p-mauve-bg: #33313d;     --hc-p-mauve-fg: #b9b3d6;
  --hc-p-neutral-bg: #262b31;   --hc-p-neutral-fg: #8a949e;

  /* standalone signals */
  --hc-danger: #ef9ba4;

  /* ---- typography tokens (R34/R35) ----
   * Copied BY VALUE from the release UI (helm-bridge/src/client/styles.css
   * --mono/--body) rather than invented: the owner's requirement is the
   * Helm's fonts throughout the Bridge, and same names + same stacks means a
   * header set in --mono here is the same face as a WaaG header there. No
   * webfont ships — the Helm's only @font-face is the terminal-pref
   * JetBrains Mono, deliberately out of scope. */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --body: Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif;

  /* ---- FONT ROLES — the consumption API (Styles × Themes, PR-1) --------
   * The same six semantic roles the Helm declares (helm-bridge/src/client/
   * styles.css), copied BY VALUE for the same reason --mono/--body are: same
   * names + same stacks means a header set in --font-display here is the
   * same face as one there. --mono/--body above are the KIT and are no
   * longer consumed directly — fonts-check refuses a raw-kit consumer.
   * Blueprint is the IDENTITY mapping; scripts/fonts-identity.mjs proves it. */
  --font-reading: var(--body);   /* long-form prose: KB/help, agent transcripts */
  --font-data:    var(--mono);   /* grids, fields, tables, metrics — tabular figures */
  --font-ui:      var(--body);   /* controls, modals, menus, tooltips, assistant chat */
  --font-display: var(--body);   /* page and section headers */
  --font-accent:  var(--mono);   /* eyebrow labels, chips, grid headers */
  --font-code:    var(--mono);   /* code blocks and terminals */
  --font-data-numeric: tabular-nums;   /* role contract: data renders tabular figures */

  /* ---- motion tokens ----
   * Copied by VALUE from the release UI (helm-bridge/src/client/styles.css
   * "motion tokens (BR-D1)") rather than invented, because the owner's
   * requirement is that the two surfaces animate alike. Same names, same
   * numbers: a 220ms reveal here is the same gesture as a 220ms reveal there.
   * These are mode-independent — motion does not change with the theme — so
   * they are declared once and NOT repeated in the light block. */
  --t-micro: 120ms;      /* hover, press, focus — acknowledgment */
  --t-standard: 220ms;   /* reveals, menus, cards */
  --t-deliberate: 360ms; /* drawers, entrances */
  --ease-out: cubic-bezier(.2, .7, .3, 1);
  --ease-in: cubic-bezier(.6, .1, .8, .4);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);

  /* ---- second-tier chrome tokens ----
   * The release UI's palette carries these and the console's did not, so the
   * borrowed components had nothing to resolve against. Written here as the
   * mode DEFAULT; appearance.ts overwrites the ones a theme defines with
   * inline custom properties (it already writes the raw --ink-2/--border-2/…
   * vocabulary onto <html>), so these are the pre-hydrate floor. */
  --hc-ink2: #93a3ba;      /* secondary text */
  --hc-border2: #2a3a55;   /* raised borders */
  --hc-accent-dim: #83bd4f2e;
  --hc-onaccent: #0c1207;
  /* link + selection idiom floors (the Helm's a{color:var(--sea)} and
   * hover --accent-2) — overwritten inline per theme by appearance.ts's
   * UI_TOKEN_VARS loop; these are the pre-hydrate values. */
  --sea: #4C8FE8;
  --accent-2: #a3d478;
  /* chat send accent (Claude terracotta) — the Helm's --send seat, copied
   * by value; a theme may override via an inline --send (themes write
   * --send, never --pi-send). Send-button INK is white in both modes, the
   * Helm's own hardcode on .pi-sendbtn. Mode-independent, redeclared in
   * the light block by the complete-set contract. */
  --send: #c96442;
  --send-ink: #fff;
  --hc-radius: 8px;        /* the Helm's --radius, copied by value */
  --hc-shadow: 0 16px 40px rgba(0, 0, 0, .45);
  --hc-hairline: rgba(255, 255, 255, .05);
  /* R36 — glow GEOMETRY for running indicators, consumed as
   * `box-shadow: var(--hc-glow) <color>`. Dark mode glows; the light block
   * zeroes it, matching the Helm's deliberate `--glow: 0 0 0`. appearance.ts
   * re-pins it per theme MODE (themeTokenVars). */
  --hc-glow: 0 0 8px;

  /* Replay-VM increment: the REPLAY badge. The SAME values in both token
   * blocks ON PURPOSE (the Bridge banner's doctrine — a replay marker must
   * look identical in every mode, never blend into a theme). */
  --hc-replay-badge-bg: #b45309;
  --hc-replay-badge-fg: #fffbeb;
  /* FW5 (VM-fork): the FORK badge — same identical-in-every-mode doctrine. */
  --hc-fork-badge-bg: #6d28d9;
  --hc-fork-badge-fg: #f5f3ff;

  /* ---- BUTTON INTENT RAMPS (Styles × Themes, PR-2) --------------------
     The SAME four intents and the SAME token names the Helm declares
     (helm-bridge/src/client/styles.css) — authored here in the console's own
     --hc-* vocabulary, for the same reason the tooltip's CSS twin is: this
     :root has no floor for the Helm's raw --surface/--ink names, so a
     verbatim copy would render unstyled on a first-ever visit. The `.ibtn`
     component block below consumes only these names and is byte-identical
     on both surfaces; buttons-check refuses a drift.

     These need no repeat in the light block: every value is a var() onto an
     --hc-* token that the light block (and appearance.ts) already re-points. */
  --btn-neutral-fill:         var(--hc-surface2);
  --btn-neutral-ink:          var(--hc-fg);
  --btn-neutral-border:       var(--hc-border2);
  --btn-neutral-hover-fill:   var(--hc-panel);
  --btn-neutral-hover-ink:    var(--hc-accent);
  --btn-neutral-hover-border: var(--hc-accent);

  --btn-additive-fill:         var(--hc-accent);
  --btn-additive-ink:          var(--hc-onaccent);
  --btn-additive-border:       var(--hc-accent);
  --btn-additive-hover-fill:   var(--accent-2);
  --btn-additive-hover-ink:    var(--hc-onaccent);
  --btn-additive-hover-border: var(--accent-2);

  --btn-destructive-fill:         color-mix(in srgb, var(--hc-danger) 10%, var(--hc-surface2));
  --btn-destructive-ink:          var(--hc-danger);
  --btn-destructive-border:       color-mix(in srgb, var(--hc-danger) 45%, transparent);
  --btn-destructive-hover-fill:   color-mix(in srgb, var(--hc-danger) 26%, var(--hc-surface2));
  --btn-destructive-hover-ink:    var(--hc-danger);
  --btn-destructive-hover-border: var(--hc-danger);

  --btn-error-fill:         color-mix(in srgb, var(--hc-p-amber-fg) 10%, var(--hc-surface2));
  --btn-error-ink:          var(--hc-p-amber-fg);
  --btn-error-border:       color-mix(in srgb, var(--hc-p-amber-fg) 45%, transparent);
  --btn-error-hover-fill:   color-mix(in srgb, var(--hc-p-amber-fg) 26%, var(--hc-surface2));
  --btn-error-hover-ink:    var(--hc-p-amber-fg);
  --btn-error-hover-border: var(--hc-p-amber-fg);

  /* ---- FORM-CONTROL FIELD TOKENS (Styles × Themes, PR-3a) -------------
     The SAME twenty slot names the Helm declares, authored here in the
     console's own --hc-* vocabulary — the `--btn-*` precedent directly
     above. The `fc-component-block` below consumes only these names and is
     byte-identical on both surfaces; primitives-check refuses a drift.
     No repeat needed in the light block: every value is a var() onto an
     --hc-* token the light block already re-points. */
  --fc-field-bg:           var(--hc-surface2);
  --fc-field-ink:          var(--hc-fg);
  --fc-field-border:       var(--hc-border2);
  --fc-field-muted:        var(--hc-muted);
  --fc-field-hover-border: var(--hc-accent);
  --fc-field-focus-border: var(--hc-accent);
  --fc-field-focus-ring:   color-mix(in srgb, var(--hc-accent) 30%, transparent);
  --fc-field-disabled-ink: var(--hc-ink2);
  --fc-pop-bg:             var(--hc-panel);
  --fc-pop-border:         var(--hc-border2);
  --fc-pop-shadow:         var(--hc-shadow);
  --fc-opt-hover-bg:       var(--hc-surface2);
  --fc-opt-on-bg:          color-mix(in srgb, var(--hc-accent) 18%, transparent);
  --fc-opt-on-ink:         var(--hc-accent);
  --fc-track-off:          var(--hc-track);
  --fc-track-on:           var(--hc-accent);
  --fc-knob:               var(--hc-muted);
  --fc-knob-on:            var(--hc-onaccent);
  --fc-ok:                 var(--hc-p-green-fg);
  --fc-bad:                var(--hc-danger);
  /* ---- SCROLLBAR + FOCUS TOKENS (Styles × Themes, PR-3c) ---------------
     The scroll-rail engine reads --sb-rail-ink / --sb-rail-accent; these
     derive the THEMED FLOOR from the same two seats using the rail's own
     mixes (track 6%, thumb 55%, hover 80%), so a surface that rides the
     floor instead of a rail still wears the rail's colours. The floor block
     itself is byte-identical in both CSS homes and primitives-nav-check
     refuses a drift.

     The focus ring is ONE treatment app-wide (the keyboard contract's
     closing row): every :focus-visible rule in either home resolves to these
     three tokens, and the gate names any rule that hand-picks instead. ---- */
  --sb-track:       color-mix(in srgb, var(--sb-rail-ink) 6%, transparent);
  --sb-thumb:       color-mix(in srgb, var(--sb-rail-accent) 55%, transparent);
  --sb-thumb-hover: color-mix(in srgb, var(--sb-rail-accent) 80%, transparent);
  --focus-ring: var(--sb-rail-accent);
  --focus-ring-width: 2px;
  --focus-ring-offset: 2px;
  --chip-ink: color-mix(in srgb, var(--hc-fg) 82%, transparent);
  --chip-mute: color-mix(in srgb, var(--hc-fg) 58%, transparent);
}

/* ---- style-axis floor (owner ruling 2026-09-06, supersedes #490) ----------
   style-materials.css (generated from the Helm's styles.css by
   ensure-style-materials.mjs, linked in index.html) carries the eight styles'
   `[data-style]` MATERIAL tables. Those tables speak the Helm's token
   vocabulary (--surface, --ink, --accent, --glass*, --radius, …), which
   appearance.ts ALREADY writes onto <html> at runtime (themeTokenVars loops
   UI_TOKEN_VARS, whose values ARE the Helm names) — so the tables resolve once
   a theme is applied.

   This block is (1) the PRE-HYDRATE floor — first-ever visit, JS disabled,
   private windows — mapping the Helm names onto the console's own --hc-*
   equivalents (which the light block + appearance.ts re-point, so they track
   the theme for free), and (2) the few STRUCTURAL tokens the material tables
   reference that appearance.ts does NOT write: --glass*, --accent-glow,
   --radius (the last three copied BY VALUE from the Helm's base :root). --sea
   and --accent-2 already live in the block above; aux-* are decoration-only
   (dropped from the extract) and are not floored here. */
:root {
  --bg: var(--hc-bg);
  --bg-deep: var(--hc-bg-deep);
  --surface: var(--hc-surface);
  --surface-2: var(--hc-surface2);
  --surface-3: var(--hc-track);
  --border: var(--hc-line);
  --border-2: var(--hc-border2);
  --ink: var(--hc-fg);
  --ink-2: var(--hc-ink2);
  --ink-3: var(--hc-muted);
  --accent: var(--hc-accent);
  --accent-dim: var(--hc-accent-dim);
  --onaccent: var(--hc-onaccent);
  --yellow: var(--hc-p-amber-fg);
  --radius: var(--hc-radius);
  /* the FORM LADDER's pre-hydrate floor — helm-bridge/src/client/styles.ts
     FORM_VAR_NAMES, copied BY VALUE from the Helm's style-axis base table
     exactly as --radius/--glass* above are. helm-bridge/scripts/form-tokens.test.mjs
     control E refuses a registry entry with no row here AND a row whose value
     has drifted from the Helm's, so this copy cannot rot in silence.
     `--radius` itself is NOT re-floored: it stays on --hc-radius (8px, the
     same value the Helm's --radius-md carries), so the console keeps one
     theme-owned radius seat. */
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 10px;
  --radius-xl: 12px;

  /* @@radius-non-steps — NOT the ladder. Four steps (sm/md/lg/xl) and
     nothing else; `pill`/`full` are SHAPES and `2xs`/`xs`/`2xl` are
     EXTREMES. Gated by radius-ladder.test.mjs. */
  --radius-2xs: 2px;
  --radius-xs: 4px;
  --radius-2xl: 14px;
  --radius-pill: 999px;
  --radius-full: 50%;
  --stroke-hair: 1;
  --stroke-mark: 1.25;
  --stroke-icon: 1.7;
  --stroke-emph: 2.2;
  --stroke-cap: round;
  --stroke-join: round;
  --stroke-miter: 4;
  --grid-step: 28px;
  --grid-w: 1px;
  --grid-dash: 0;
  --seg-lit: 100%;
  --seg-gap: 0px;
  --seg-pitch: 100%;
  --mark-rendering: auto;
  --accent-glow: color-mix(in srgb, var(--accent) 42%, transparent);
  --glass: color-mix(in srgb, var(--surface) 60%, transparent);
  --glass-brd: color-mix(in srgb, var(--border-2) 62%, transparent);
  --glass-hi: color-mix(in srgb, #ffffff 7%, transparent);
  --glass-sh: 0 18px 50px -14px rgba(0, 0, 0, .6);
}

/* THE MARK TOKEN FLOOR — `--mark-*` (S3, #1037). The console's own half of
   the twelve-token transport, in the console's OWN vocabulary. The per-style
   `--mark-*` tables cross from the Helm inside style-materials.css (they are
   custom properties in a `:root[data-style]` table, which is exactly what
   ensure-style-materials.mjs carries); this FLOOR does not cross, because the
   two homes genuinely diverge and a single floor would hide that.

   THE RECORDED DIVERGENCE, measured: `.seismo-base` strokes `--border-2` on
   the Helm and `--hc-line` here, and `--s-completed` does not exist here at
   all (`grep -c -- '--s-completed' helm-console/src/client/styles.css` -> 0),
   which is why `--mark-done` is `var(--hc-p-green-fg)` rather than a name
   nothing else on this surface needs. Recording the divergence is the point;
   closing it would move a Blueprint pixel.

   Every value is the value its consumer already carried below, so this block
   moves ZERO Blueprint pixels. C2: no `--pi-*` here, ever. */
:root {
  --mark-radius:     3px;
  --mark-stroke-w:   1;
  --mark-cap:        round;
  --mark-join:       round;
  --mark-rendering:  auto;
  --mark-ring-w:     3;
  --mark-track:      var(--hc-track);
  --mark-track-brd:  var(--hc-line);
  --mark-fill:       var(--hc-accent);
  --mark-fill-2:     var(--hc-accent);
  --mark-grid:       var(--hc-line);
  --mark-done:       var(--hc-p-green-fg);
}

:root[data-theme='light'] {
  /* the Helm's light-slot --glass* (base :root light block), copied by value.
     The vocabulary floors above are mode-independent (var() onto --hc-*), so
     only the glass literals need a light override. */
  --glass: color-mix(in srgb, var(--surface) 74%, transparent);
  --glass-brd: color-mix(in srgb, var(--border-2) 70%, transparent);
  --glass-hi: color-mix(in srgb, #ffffff 55%, transparent);
  --glass-sh: 0 18px 44px -16px rgba(40, 34, 20, .35);
  --hc-bg: #f7f8fa;
  /* shell chrome — see the dark block's note; one ramp step darker. */
  --hc-bg-deep: #edeff2;
  --hc-fg: #1c2229;
  --hc-accent: #1a7f4b;
  --hc-muted: #5b636c;
  --hc-line: #d5dae0;
  --hc-surface: #ffffff;
  --hc-surface2: #eef1f4;
  --hc-panel: #ffffff;
  --hc-track: #e2e6ea;
  --hc-scrim: rgba(20, 26, 33, 0.38);

  /* Pale ground + saturated ink, so the ladder keeps its hue identity while
   * clearing contrast on a light page. */
  --hc-p-blue-bg: #dbe8ff;      --hc-p-blue-fg: #1c4680;
  --hc-p-cyan-bg: #d6ecfa;      --hc-p-cyan-fg: #14567a;
  --hc-p-green-bg: #d3f0e0;     --hc-p-green-fg: #14683f;
  --hc-p-olive-bg: #e4f0d4;     --hc-p-olive-fg: #43631f;
  --hc-p-amber-bg: #f7ecd2;     --hc-p-amber-fg: #7a5514;
  --hc-p-red-bg: #fadcdf;       --hc-p-red-fg: #8c2733;
  --hc-p-violet-bg: #ece0f7;    --hc-p-violet-fg: #593a80;
  --hc-p-mauve-bg: #e6e3ee;     --hc-p-mauve-fg: #4a4360;
  --hc-p-neutral-bg: #e4e7ea;   --hc-p-neutral-fg: #545c64;

  --hc-danger: #a32330;

  /* Typography is mode-independent; redeclared here BY CONTRACT (the seam
   * plan pins the pair in both blocks) so the light block stays a COMPLETE
   * token set — the property this file's header promises. Same values. */
  --mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --body: Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans", source-sans-pro, sans-serif;

  /* ---- FONT ROLES — the consumption API (Styles × Themes, PR-1) --------
   * The same six semantic roles the Helm declares (helm-bridge/src/client/
   * styles.css), copied BY VALUE for the same reason --mono/--body are: same
   * names + same stacks means a header set in --font-display here is the
   * same face as one there. --mono/--body above are the KIT and are no
   * longer consumed directly — fonts-check refuses a raw-kit consumer.
   * Blueprint is the IDENTITY mapping; scripts/fonts-identity.mjs proves it. */
  --font-reading: var(--body);   /* long-form prose: KB/help, agent transcripts */
  --font-data:    var(--mono);   /* grids, fields, tables, metrics — tabular figures */
  --font-ui:      var(--body);   /* controls, modals, menus, tooltips, assistant chat */
  --font-display: var(--body);   /* page and section headers */
  --font-accent:  var(--mono);   /* eyebrow labels, chips, grid headers */
  --font-code:    var(--mono);   /* code blocks and terminals */
  --font-data-numeric: tabular-nums;   /* role contract: data renders tabular figures */

  --hc-ink2: #4e5a6c;
  --hc-border2: #c4bba6;
  --hc-accent-dim: #497f1d24;
  --hc-onaccent: #fdfcf8;
  /* link/selection + send floors — see the dark block's notes. */
  --sea: #1F63C8;
  --accent-2: #3a660f;
  --send: #c96442;
  --send-ink: #fff;
  --hc-shadow: 0 16px 40px rgba(28, 34, 41, .18);
  --hc-hairline: rgba(0, 0, 0, .05);
  /* Glow OFF in light mode — the Helm's own --glow is 0 in light (ruling
   * Q4's dispatched default; the owner may override). */
  --hc-glow: 0 0 0;

  /* Replay badge: identical to the dark block by doctrine (see above). */
  --hc-replay-badge-bg: #b45309;
  --hc-replay-badge-fg: #fffbeb;
  /* FORK badge: identical to the dark block by doctrine (see above). */
  --hc-fork-badge-bg: #6d28d9;
  --hc-fork-badge-fg: #f5f3ff;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* Style-axis GROUND (owner ruling 2026-09-06): the body paints the chosen
     style's background layer, exactly as the Helm's body does. Blueprint's
     --bgl-layer is the shipped radial; canvas styles degrade to their CSS-only
     still here (the Bridge home runs no background canvas — the ruling permits
     it). Fallbacks keep the flat --hc-bg if style-materials.css is absent. */
  background-color: var(--bgl-color, var(--hc-bg));
  background-image: var(--bgl-layer, none);
  background-attachment: var(--bgl-attach, scroll);
  /* The three background LONGHANDS (#773). A ground that is a STACK carries its
     geometry PER LAYER, and `--bgl-layer` cannot hold a size list, a position
     list or a repeat mode. Synthwave's held frame — what the reduce lanes paint
     in place of the torn-down canvas — is eleven layers whose whole gesture is
     its position list, so without these three the home renders the flat ramp.
     Same fallback idiom as the three lines above: a style declaring none of
     them paints unchanged. */
  background-size: var(--bgl-size, auto);
  background-position: var(--bgl-position, 0% 0%);
  background-repeat: var(--bgl-repeat, repeat);
  color: var(--hc-fg);
  /* R35: the Helm's body stack, via the token — one of FOUR divergent
     hardcoded stacks this file used to carry (body, .hc-detail, the audit
     action pills, .hc-subject-id), all now pointed at --body/--mono. */
  font: 14px/1.5 var(--font-ui);
}

/* ---- native controls ride the UI role (the style axis' blind spot) ----
   `button`, `input`, `select` and `textarea` do NOT inherit `font` from
   their parent: the UA stylesheet gives each its own default family, so a
   control nobody wrote a font rule for renders in the BROWSER's face under
   every style — Arial in Chromium — while the whole page around it obeys
   `--font-ui`. Measured 2026-09-07 by the style-parity probe: a `<button>`
   on the Bridge home resolved Arial under all 8 styles, INCLUDING Blueprint
   (the identity control's only red). It was invisible to `fonts-check`
   because that gate scans DECLARATIONS, and the defect is the absence of
   one; `fonts-check`'s native-control arm now refuses this rule's absence.

   `font-family`, deliberately NOT the `font` shorthand: the shorthand also
   resets font-size to the UA default and this file's own geometry pin
   (`button.hc-fw-go`, bridge-ui-polish item A) records what that costs.
   The Helm carries the twin rule at styles.css:462 — it had the same hole
   for `select`, closed in the same commit. */
button, input, select, textarea, optgroup { font-family: var(--font-ui); }

/* ---- global idioms (theme-truth, copied from the Helm's styles.css) ----
   Links ride --sea and warm to --accent-2 on hover; ONE app-wide keyboard
   ring; text selection rides the accent wash. Controls that declare their
   own color (nav links, cards, grid links) keep it — these are floors. */
a { color: var(--sea); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { color: var(--accent-2); }
:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-offset); border-radius: 3px; }

/* >>> composer-block — THE COMPOSER IS ITS OWN PRIMITIVE >>> */
/* A chat composer is NOT a form control, and the primitive catalog's
   standard-input treatment must not reach it (owner ruling 2026-09-07: "this
   field should not be standardized to a standard input box … I should not see
   a selectable border around the input").

   WHAT WENT WRONG. Every style arm ships a BLANKET focus treatment —
   `:root[data-style="…"] :focus-visible` (0,3,0), and STUDIO's
   `… input:focus-visible, … textarea:focus-visible` (0,3,1). Both outrank
   `.pi-card textarea { outline: none }` (0,1,1) and
   `.as-input:focus-visible { outline: none }` (0,2,0), so from the style axis
   onward the transcript composer and the assistant composer grew a
   standard-input focus ring: a plain outline under Glass/Starship, a floating
   double ring under Studio, a lamplit box-shadow under Quarterdeck, a
   square-cornered 3px double rule under Mainframe — the style the owner was
   on. Blueprint alone was unaffected, because its bare `:focus-visible` is
   (0,1,0) and LOSES to the composer's own rules. That is why this read as a
   regression introduced by the standardization work rather than as an old bug.

   THE EXEMPTION is written here, once, at (0,4,0) — higher than every blanket
   arm, present and future — rather than as eight `:not()` patches a ninth
   style would forget. The composer keeps its own affordance: `.pi-card:focus-
   within` strengthens the pill's border and `.as-input:focus-visible`
   strengthens the bubble's, both resolved through the acting style's token
   graph, so every style still says "focused" in its own materials.
   `--composer-radius` exists because three style arms set `border-radius: 0`
   inside that blanket rule, which squared the assistant composer's 22px pill
   the instant it took focus.

   This block is BYTE-IDENTICAL between the two stylesheet homes, and
   primitives-check's composer arm refuses the drift — the same contract the
   fc-component-block carries. */
:root .composer .composer-field {
  --composer-radius: 0px;
  resize: none;
}
:root .composer .as-input { --composer-radius: 22px; }
:root .composer .composer-field:focus,
:root .composer .composer-field:focus-visible {
  outline: none;
  box-shadow: none;
  border-radius: var(--composer-radius);
}
/* <<< composer-block <<< */
::selection { background: var(--hc-accent-dim); }

.hc-header {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 20px;
  /* SHELL material tier (owner ruling 2026-09-06): the app chrome rides the
     chosen style's shell material — Blueprint's is the theme-truth deep ground
     step with the shell's blur/coating; other styles bring their own. Fallbacks
     keep the flat deep ground if style-materials.css is absent. */
  background: var(--mat-shell-bg, var(--hc-bg-deep));
  border-bottom: 1px solid var(--mat-shell-brd, var(--hc-line));
  backdrop-filter: var(--mat-shell-blur, none);
  -webkit-backdrop-filter: var(--mat-shell-blur, none);
  box-shadow: var(--mat-shell-sh, none);
}

.hc-brand { font-weight: 600; letter-spacing: 0.02em; }
.hc-slots { color: var(--hc-accent); font-variant-numeric: tabular-nums; }
.hc-user { margin-left: auto; color: var(--hc-muted); }

.hc-main { padding: 24px 20px; }
.hc-placeholder { color: var(--hc-muted); }

/* --- admin (T1.4) --- */
.hc-admin section { margin-bottom: 32px; }
.hc-admin h2 { font-size: 16px; margin: 0 0 12px; }
.hc-table { border-collapse: collapse; width: 100%; }
.hc-table th, .hc-table td {
  text-align: left; padding: 6px 10px; border-bottom: 1px solid var(--hc-line);
}
.hc-table th { color: var(--hc-muted); font-weight: 500; }
.hc-row-disabled { color: var(--hc-muted); text-decoration: line-through; }
/* .hc-invite / .hc-audit-filter retired 2026-09-05 (WF-A audit item 4): zero
   references in any .ts or .html on this surface — the invite and audit-filter
   markup they styled is long gone, and they carried the .hc-btn selector list
   twice. .hc-table select keeps its rule. */
.hc-table select {
  background: var(--hc-surface); color: var(--hc-fg); border: 1px solid var(--hc-line);
  border-radius: var(--radius-xs); padding: 4px 8px;
}
.hc-btn {
  background: var(--hc-surface2); color: var(--hc-fg); border: 1px solid var(--hc-line);
  border-radius: var(--radius-xs); padding: 4px 10px; cursor: pointer;
}
.hc-btn:hover { border-color: var(--hc-accent); }
.hc-detail { font-family: var(--font-data); font-size: 12px; }
.hc-status { color: var(--hc-muted); }
.hc-brand a { color: var(--hc-fg); text-decoration: none; }

/* --- home grid (T1.8) --- */
/* Frame = the Helm's .gl-grid in console tokens: 1px border + --hc-radius
   (8px, the Helm's --radius), content clipped to the rounding. */
.hc-grid {
  height: calc(100vh - 110px); min-height: 320px;
  border: 1px solid var(--hc-line); border-radius: var(--hc-radius);
  overflow: hidden;
}
/* The Brain page stacks two grids inside sections (runs, claims): each takes
   a bounded height of its own, never the page's. */
.hc-grid.hc-brain-grid { height: 320px; min-height: 200px; }
/* Browse (the Brain page's landing view): the type list beside the entity
   table. A type with 0 entities still shows, muted; the active one carries
   the accent. The id cells and the entity page's graph rows are links. */
.hc-brain-browse { display: flex; gap: 16px; align-items: flex-start; }
.hc-brain-typelist { flex: 0 0 200px; max-height: 420px; overflow: auto; border: 1px solid var(--hc-line); border-radius: var(--hc-radius); }
.hc-brain-types { list-style: none; margin: 0; padding: 4px 0; }
.hc-brain-type { display: flex; justify-content: space-between; gap: 8px; padding: 2px 10px; }
.hc-brain-type-a { color: var(--hc-fg); text-decoration: none; }
.hc-brain-type-a:hover { color: var(--hc-accent); }
.hc-brain-type-n { color: var(--hc-muted); font-variant-numeric: tabular-nums; }
.hc-brain-type-zero .hc-brain-type-a { color: var(--hc-muted); }
.hc-brain-type-on { background: var(--hc-surface2); }
.hc-brain-type-on .hc-brain-type-a { color: var(--hc-accent); }
.hc-brain-type-unschema .hc-brain-type-a { font-style: italic; }
.hc-brain-browse-main { flex: 1 1 auto; min-width: 0; }
.hc-brain-browsebar { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-bottom: 6px; }
.hc-brain-page { color: var(--hc-muted); font-variant-numeric: tabular-nums; }
.hc-brain-empty { color: var(--hc-p-amber-fg); }
.hc-brain-refusal { color: var(--hc-danger); }
.hc-brain-link { color: var(--hc-accent); }
@media (max-width: 720px) {
  .hc-brain-browse { flex-direction: column; }
  .hc-brain-typelist { flex-basis: auto; width: 100%; max-height: 180px; }
}
.hc-pill {
  display: inline-block; padding: 1px 10px; border-radius: var(--radius-lg);
  font-size: 12px; line-height: 18px; text-transform: capitalize;
}
.hc-pill-ideating { background: var(--hc-p-blue-bg); color: var(--hc-p-blue-fg); }
.hc-pill-active { background: var(--hc-p-green-bg); color: var(--hc-p-green-fg); }
.hc-pill-completed { background: var(--hc-p-mauve-bg); color: var(--hc-p-mauve-fg); }
.hc-pill-aborted { background: var(--hc-p-red-bg); color: var(--hc-p-red-fg); }
/* Mid-transition ('shutting down' / 'starting' / 'verifying'). The pill keeps
   its terminal-status colour — a release must not appear to change state for a
   transient reason — and gains a soft pulse so the grid reads as BUSY rather
   than as a new status. Motion is opt-out for reduced-motion users. */
.hc-pill-busy { outline: 1px dashed currentColor; outline-offset: 1px; opacity: .85; animation: hc-pill-pulse 1.6s ease-in-out infinite; }
/* #4246 P1 — 'corpus failure': the last corpus upgrade on this release FAILED
   (transition_error carries vm-standing's CORPUS_FAILURE_PREFIX). The status
   did not change, so the pill keeps its status colour and takes the failure
   red as a solid outline — no pulse: nothing is in progress. */
.hc-pill-corpus-failure { outline: 2px solid var(--hc-p-red-fg); outline-offset: 1px; background: var(--hc-p-red-bg); color: var(--hc-p-red-fg); }
.hc-vm-corpus-failure { color: var(--hc-p-red-fg); }
@keyframes hc-pill-pulse { 0%, 100% { opacity: .85; } 50% { opacity: .55; } }
/* .hc-pill-busy's reduced-motion opt-out lives in THE one guard at the end of
   this file — the stray per-rule guard that used to sit here was folded in by
   design-system-foundation (one guard, one selector per line, alphabetical). */
.hc-rel-link { color: var(--hc-fg); }
.hc-branches-pending { color: var(--hc-muted); font-size: 11px; }
/* Replay-VM increment — the REPLAY row badge. Hard-coded high contrast on
 * purpose (the Bridge banner's doctrine): identical in every theme, never
 * mistakable for a status pill. */
.hc-badge-replay {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 3px;
  background: var(--hc-replay-badge-bg);
  color: var(--hc-replay-badge-fg);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  vertical-align: middle;
}
/* DEAD-IDLE (REL-096) — the Fleet-cell badge is the chip primitive
 * (mkChip, error intent, solid tone) in home.ts; it carries no rule of its
 * own here. Chips, badges and status pills are ONE family on the --btn-*
 * ramps and the primitive-catalog gate refuses a hand-rolled one by name. */
/* FW5 (VM-fork) — the FORK row badge: same doctrine, its own color (a fork
 * is a real release; the badge is lineage, not a warning). */
.hc-badge-fork {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 3px;
  background: var(--hc-fork-badge-bg);
  color: var(--hc-fork-badge-fg);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.12em;
  vertical-align: middle;
}
/* The fork VERIFY / BOOT-GATE modifiers (forkBadgeModel): the semantic
 * pill ladder's own colors, so light mode follows for free — green
 * "FORK ✓ 7/7", amber "FORK 5/7" / "(unbooted)", red "FORK GATE REFUSED".
 * The word is always present: color alone is never the signal. */
.hc-badge-fork-ok { background: var(--hc-p-green-bg); color: var(--hc-p-green-fg); }
.hc-badge-fork-warn { background: var(--hc-p-amber-bg); color: var(--hc-p-amber-fg); }
.hc-badge-fork-bad { background: var(--hc-p-red-bg); color: var(--hc-p-red-fg); }

/* REL-096/097 — the escalation SCORECARD badge on the Name cell and the
 * card: "N decisions waiting" LOUD (a human is being asked — the run's
 * positive outcome), the plain open count quiet. Semantic-ladder amber, a
 * bordered chip — never mistakable for a status pill or the fixed-color
 * REPLAY/FORK lineage badges. The tip carries the class counts. */
.hc-badge-esc {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 3px;
  border: 1px solid color-mix(in srgb, currentColor 40%, transparent);
  color: var(--hc-p-amber-fg);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  vertical-align: middle;
  white-space: nowrap;
}
.hc-badge-esc-loud {
  background: color-mix(in srgb, var(--hc-p-amber-fg) 18%, transparent);
  border-color: var(--hc-p-amber-fg);
}
.hc-card-esc { margin-top: 4px; }
.hc-card-esc .hc-badge-esc { margin-left: 0; }

/* Run position (aisdlc-docs § 30) on the Jump-Back-In card: the step chip
 * ("S10 implement"), the state word, and ONE text cell (the blocker while
 * blocked, else the lever) — the Bridge's own line, verbatim in the tip.
 * Tones follow the Helm strip: live/done green, dead the amber attention
 * chip, stale/neutral muted. Wraps on phones; the text clamps to two lines
 * so a long blocker never grows the card past its neighbours. */
.hc-card-rp {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px;
  margin-top: 4px; font-size: 11px; line-height: 1.3; color: var(--hc-muted, inherit);
  min-width: 0;
}
.hc-rp-step, .hc-rp-state {
  display: inline-block; padding: 1px 6px; border-radius: 3px;
  border: 1px solid color-mix(in srgb, currentColor 40%, transparent);
  font-size: 10px; font-weight: 700; letter-spacing: 0.06em; white-space: nowrap;
}
.hc-rp-text {
  flex: 1 1 100%; min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}
.hc-rp-tone-live .hc-rp-step, .hc-rp-tone-live .hc-rp-state,
.hc-rp-tone-done .hc-rp-step, .hc-rp-tone-done .hc-rp-state { color: var(--hc-p-green-fg, currentColor); }
.hc-rp-tone-dead .hc-rp-state {
  color: var(--hc-p-amber-fg);
  background: color-mix(in srgb, var(--hc-p-amber-fg) 18%, transparent);
  border-color: var(--hc-p-amber-fg);
}
.hc-rp-tone-dead .hc-rp-step { color: var(--hc-p-amber-fg); }

/* Test-server status (item 41): dot + word, colored by the semantic ladder
   above — green Live / amber Deploying / red Failed / red Offline — so light
   mode follows for free and no hue is invented. The word is always present:
   color alone is never the signal, and Failed vs Offline share a hue for
   exactly that reason: they are told apart by the WORD and the tooltip, not
   by inventing a fifth colour. Failed does NOT breathe — the glow is the
   running indicator (R36) and a broken chain is not running. */
.hc-ts { display: inline-flex; align-items: center; gap: 6px; }
.hc-ts-dot { width: 8px; height: 8px; border-radius: var(--radius-full); background: currentColor; flex: none; }
.hc-ts-live { color: var(--hc-p-green-fg); }
.hc-ts-deploying { color: var(--hc-p-amber-fg); }
.hc-ts-failed { color: var(--hc-p-red-fg); }
.hc-ts-offline { color: var(--hc-p-red-fg); }
.hc-ts-open {
  display: inline-flex; align-items: center; color: inherit;
  border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
  border-radius: var(--radius-sm); padding: 1px 3px;
}
.hc-ts-open:hover { border-color: currentColor; }
.hc-ts-open .icon { width: 13px; height: 13px; }

/* VM status column (owner request 2026-09-03): the test-server dot pattern
   applied to the release VM — same .hc-ts layout + .hc-ts-dot, its own
   color classes. 'none' is MUTED, not red: no machine is a normal state. */
.hc-vm-running { color: var(--hc-p-green-fg); }
.hc-vm-provisioning { color: var(--hc-p-amber-fg); }
.hc-vm-stopped { color: var(--hc-p-red-fg); }
.hc-vm-gone { color: var(--hc-p-red-fg); font-weight: 600; }
/* #1107: EC2 running, box not answering (impaired / N silent sweeps) — same red, named by time + arm. */
.hc-vm-unreachable { color: var(--hc-p-red-fg); font-weight: 600; }
/* #3288: the transition outlived the lane's server-published budget, so no
   lane is still working on it. Red + a pointer, because this cell is the one
   VM cell that is an ACTION (click -> transition/reconcile). */
.hc-vm-stale { color: var(--hc-p-red-fg); font-weight: 600; cursor: pointer; text-decoration: underline dotted; }
.hc-vm-none { color: var(--hc-muted); }

/* R25 / migration 055: the uptime cell's bound qualifier ("≤" backfilled
   from EC2 LaunchTime, "≥" observed-already-running) — muted so the number
   stays the reading, but present so a bounded baseline never impersonates
   the console's own stamp. Static text: no animation, nothing for the
   reduced-motion guard. */
.hc-uptime-bound { color: var(--hc-muted); }

/* Column picker (item 41): a plain checkbox menu under the header button.
   Panel/line/shadow tokens only — themes for free. */
.hc-colswrap { position: relative; }
.hc-colsmenu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  min-width: 220px; max-height: 60vh; overflow-y: auto; padding: 8px;
  /* ONE COLUMN (owner screenshot 2026-09-08): the rows are .fc-tg toggles,
     which are inline-flex — in a block box they flowed two to a line whenever
     a label was short ("Name [toggle] ID"). A flex column gives every row its
     own line whatever its width. The 60vh is the static floor; mountColumnPicker
     (grid-toolbar.ts) tightens max-height to the viewport below the trigger
     at open, and the themed rail draws because this class is a declared
     surface in rail-registry.ts (B12). */
  display: flex; flex-direction: column; align-items: stretch; gap: 0;
  background: var(--hc-panel); border: 1px solid var(--hc-line);
  border-radius: var(--hc-radius); box-shadow: var(--hc-shadow);
}
.hc-colsmenu[hidden] { display: none; }
.hc-colsmenu > .fc-tg {
  width: 100%; justify-content: flex-start; flex: none;
  padding: 4px 6px; border-radius: var(--radius-sm); font-size: 12px;
}
/* No row hover paint here: the fc component block owns the toggle's colour (primitives-check, layout-only skins). */

/* framework drift (the fleet Update lane). Amber = behind the newest
   published bundle and actionable; neutral = we could not measure, which is
   NOT the same as current and must not look reassuring. Both hues come from
   the semantic ladder above, so light mode follows for free. */
.hc-pill-drift { background: var(--hc-p-amber-bg); color: var(--hc-p-amber-fg); }

/* #4199 P2 — the plan-kick chip beside the status pill. Amber = owed (the
   re-arm is still armed and will fire by itself); red = BLOCKED (the console
   stopped re-arming a deterministic refusal, or hit the ceiling — the tip is
   the remedy and the Retry beside it is the only way forward). Same ladder
   as the drift pill, so light mode follows for free. */
.hc-status-cell { display: inline-flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.hc-pill-plankick-owed { background: var(--hc-p-amber-bg); color: var(--hc-p-amber-fg); }
.hc-pill-plankick-blocked { background: var(--hc-p-red-bg); color: var(--hc-p-red-fg); }

/* Fleet-level drift alert in the header. Amber, and hidden entirely when the
   fleet is current — a permanent "0 need update" reads the same whether the
   sweep proved everything current or never measured, and trains the eye to
   ignore the one control that is supposed to shout. Same hue as the per-row
   pill so the two obviously refer to the same thing. */
.hc-driftpill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: var(--radius-pill); cursor: pointer;
  font-size: 11px; font-weight: 600; letter-spacing: .01em;
  background: var(--hc-p-amber-bg); color: var(--hc-p-amber-fg);
  border: 1px solid color-mix(in srgb, var(--hc-p-amber-fg) 35%, transparent);
}
.hc-driftpill::before { content: ""; width: 6px; height: 6px; border-radius: var(--radius-full); background: currentColor; }
.hc-driftpill:hover { border-color: currentColor; }
/* The fleet-update control beside the drift pill (same visibility rule):
   neutral ramp — it opens a dry-run preview first, it is not itself the
   alarm. .hc-busy while a pass is in flight (a pass holds for one probe per
   attempted release). */
.hc-fleetupd { background: var(--hc-p-neutral-bg, transparent); color: var(--hc-fg); }
.hc-fleetupd::before { display: none; }
.hc-fleetupd.hc-busy { opacity: .6; cursor: progress; }
/* 'Update blocked' — a drift row whose VM cannot take the bundle (base
   mismatch). Amber like drift (it IS behind), dashed so the eye reads
   "cannot", never the solid pill that means "click to install". */
.hc-pill-blocked { border: 1px dashed currentColor; }

/* "Show more" for the finished tail — mirrors the release switcher's
   .rc-menu-more, the house pattern for exactly this split. */
.hc-listmore-wrap { display: flex; justify-content: center; padding: 10px 0 2px; }
/* colour retired to the button intent ramp (Styles × Themes PR-2) */
.hc-listmore {
  padding: 4px 12px; font-size: 11px; border-radius: var(--radius-pill); cursor: pointer;
}
/* .hc-listmore:hover: colour retired to the button intent ramp (Styles × Themes PR-2); the class survives as a selector hook only. */
.hc-pill-unknown { background: var(--hc-p-neutral-bg); color: var(--hc-p-neutral-fg); }
.hc-fw { display: inline-flex; align-items: center; gap: 8px; }
/* .hc-fw-update (the separate Update button) died with the R15 merge —
   the pill is the affordance; its rules live in the framework-column
   anchor below. */

/* live fleet columns. The waveform markup comes from the Bridge's own
   seismoSvg (copied verbatim at prebuild), so the class names below are ITS
   class names — restyled onto --hc-* tokens because the console does not
   share the Bridge's token namespace, and this file forbids bare hexes. */
.hc-fleet { display: inline-flex; align-items: center; gap: 12px; }
.hc-count {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12px; color: var(--hc-muted); font-variant-numeric: tabular-nums;
}
.hc-count.on { color: var(--hc-accent); }
/* corpus unpublished: N commits — the release VM holds corpus history the
   corpus remote lacks (a fork cut at that history refuses). Danger ink so
   the count reads as the warning it is, never as a fourth idle counter. */
.hc-count.hc-corpus-unpublished { color: var(--hc-danger); white-space: nowrap; }
/* The RED-disk chip (REL-096). It paints ONLY when the VM's volume is at or
   past DISK_RED_PCT: a quiet row with 0 sessions and 0 panes is exactly what
   a wedged, disk-full VM looks like, and this is the one pixel that tells
   them apart. Destructive tone, not the accent — this is not activity. */
.hc-count.hc-disk-red {
  color: var(--hc-danger);
  font-weight: 600;
  padding: 0 5px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--hc-danger) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--hc-danger) 40%, transparent);
}
/* The fleet chips' glyphs. They are SPRITE references now (home.ts
   fleetIcon()), not hand-inlined art, so they need `.icon`'s 24-viewBox
   pure-stroke contract as well as the 13px box the chip row was laid out
   for. The contract is repeated here rather than written `class="icon
   hc-ic"` at the call site: `.hc-ic` PRECEDES `.icon` in this file and the
   two selectors are equal specificity, so `.icon`'s `width: 1em` would win
   the cascade and the chips would resize. */
.hc-ic {
  width: 13px; height: 13px; flex: none;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.hc-wave { display: inline-flex; align-items: center; }
.hc-wave .seismo-svg {
  display: block; border-radius: var(--mark-radius);
  background: var(--mark-track); border: 1px solid var(--mark-track-brd);
  shape-rendering: var(--mark-rendering);
}
.hc-wave .seismo-base { stroke: var(--mark-grid); stroke-width: var(--mark-stroke-w); }
.hc-wave .seismo-area { fill: color-mix(in srgb, var(--mark-fill) 26%, transparent); stroke: none; }
.hc-wave .seismo-line { fill: none; stroke: var(--mark-fill); stroke-width: 1.25; stroke-linejoin: var(--mark-join); }
.hc-wave .seismo-bar { fill: color-mix(in srgb, var(--mark-fill-2) 70%, transparent); }

/* radial completion gauge (the console twin of the Bridge's dash-ring) */
.hc-ringwrap { display: inline-flex; align-items: center; gap: 4px; }
.hc-ring { width: 14px; height: 14px; flex: none; }
.hc-ring .ring-bg { fill: none; stroke: var(--mark-track); stroke-width: var(--mark-ring-w); }
.hc-ring .ring-fg { fill: none; stroke: var(--mark-fill); stroke-width: var(--mark-ring-w); stroke-linecap: var(--mark-cap); }
.hc-ring .ring-fg.full { stroke: var(--mark-done); }
.hc-ringn { font-size: 11px; color: var(--hc-muted); font-variant-numeric: tabular-nums; }
.hc-action { margin-right: 6px; text-transform: capitalize; }
.hc-admin-link { color: var(--hc-muted); margin-left: 8px; }
.hc-modal {
  position: fixed; inset: 0;
  /* MODAL scrim material tier (owner ruling 2026-09-06): the world behind a
     dialog goes inert in the chosen style's scrim. Fallbacks keep the flat
     scrim if style-materials.css is absent. */
  background: var(--mat-modal-scrim, var(--hc-scrim));
  backdrop-filter: var(--mat-modal-scrim-blur, none);
  -webkit-backdrop-filter: var(--mat-modal-scrim-blur, none);
  display: flex; align-items: center; justify-content: center;
  z-index: 1000;
}
/* The `hidden` attribute must win over the class's display:flex — without
   this the empty overlay stays fixed over the whole viewport (invisible on a
   dark bg) and intercepts every click, so the page reads as fully disabled. */
.hc-modal[hidden] { display: none; }
.hc-dialog p { white-space: pre-line; }
.hc-dialog {
  /* MODAL material tier (owner ruling 2026-09-06): the dialog panel itself.
     Blueprint's is the opaque surface; other styles bring their own. Fallbacks
     keep the panel look if style-materials.css is absent. */
  background: var(--mat-modal-bg, var(--hc-panel));
  border: 1px solid var(--mat-modal-brd, var(--hc-line));
  backdrop-filter: var(--mat-modal-blur, none);
  -webkit-backdrop-filter: var(--mat-modal-blur, none);
  border-radius: var(--radius-md);
  /* Viewport clamp (audit Lane A, item 9): a 480px panel exceeds a ~360px
     phone. max-width (not width) keeps the content-sizing that plain confirm/
     notice dialogs rely on — on a wide viewport this still resolves to 480px,
     and only clamps below ~512px. The specialized form/branch/timeline dialogs
     set their own fixed width and lift this cap, so it never binds them. */
  padding: 20px 24px; max-width: min(480px, calc(100vw - 32px));
}
.hc-dialog-actions { display: flex; gap: 8px; margin-top: 14px; }
/* --- Create-release form (seam create-release-modal, R50/R53-R55) ------ */
.hc-create-form label { display: block; margin: 8px 0; }
.hc-create-sub { color: var(--hc-muted); font-size: 11px; font-weight: 400; }
.hc-create-form input {
  display: block; width: 100%; margin-top: 4px;
  background: var(--hc-surface); color: var(--hc-fg);
  border: 1px solid var(--hc-line); border-radius: var(--radius-xs); padding: 6px 8px;
  transition: border-color var(--t-micro) var(--ease-out),
              box-shadow var(--t-micro) var(--ease-out);
}
/* The branch + SHA field rows (owner batch, seam:create-modal-fields): a
   full-width field (.hc-field-wrap, flex:1) with its Browse icon button
   pinned to the right on the SAME row. Both rows share this rule so the two
   fields render the SAME length and their browse buttons (identical
   .hc-btn-icon squares) line the right edges up. */
.hc-create-branchrow,
.hc-create-sharow { display: flex; gap: 6px; align-items: center; margin-top: 4px; }
/* R54 — the first-class INVALID state: danger border + a one-beat shake on
   the valid->invalid EDGE only (the class toggles per verdict change, so
   typing inside an already-invalid name never re-shakes). */
.hc-create-form input.hc-input-invalid {
  border-color: var(--hc-danger);
  box-shadow: 0 0 0 1px var(--hc-danger) inset;
  animation: hc-field-shake var(--t-standard) var(--ease-out);
}
@keyframes hc-field-shake {
  25% { transform: translateX(-3px); }
  75% { transform: translateX(2px); }
}
/* The verdict text row is GONE (owner item 1, seam:create-modal-fields):
   verdicts are in-field icons (.hc-vicon) whose sentences ride data-tip /
   aria-label / the visually-hidden live line. The hint keeps a fixed
   one-line reservation (D4 — the modal never changes size). */
.hc-create-hint {
  margin: 4px 0 0; font-size: 11px; color: var(--hc-muted);
  height: 14px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
/* Candidate chips — did-you-mean as one-click fills (both the instant lane
   and the create 400's candidates land here). Staggered entrance: the
   builder seats each chip's animation-delay; 'backwards' holds frame 0. */
.hc-create-cands {
  display: flex; flex-wrap: nowrap; gap: 6px; align-items: center;
  height: 30px; margin-top: 6px; overflow-x: auto; overflow-y: hidden;
}
.hc-cand {
  background: var(--hc-surface2); color: var(--hc-fg);
  border: 1px solid var(--hc-line); border-radius: var(--radius-pill);
  padding: 2px 10px; font-size: 12px; cursor: pointer;
  animation: hc-cand-in var(--t-standard) var(--ease-out) backwards;
  transition: border-color var(--t-micro) var(--ease-out);
}
.hc-cand:hover { border-color: var(--hc-accent); }
@keyframes hc-cand-in {
  from { opacity: 0; transform: translateY(3px); }
}
.hc-create-msg { color: var(--hc-danger); height: 32px; margin: 6px 0 0; overflow-y: auto; }

/* --- WS7: Deployments (T7.12) --- */
/* Work at a Glance moved to the Bridge client (a tab inside a release), and
   took its pill/select/toggle/union rules with it — it paints from the
   Bridge's own `--s-<status>` token ladder now instead of a second hard-coded
   palette. What stays here is what the Deployments tab and the home grid
   still use: .hc-crumb, .hc-pill (base + the -dep-/-status variants those two
   build), and .hc-progress*. */
.hc-crumb { color: var(--hc-muted); margin: 0 4px; }

/* Deployments moved to the Bridge client as a release tab and took its
   rules with it — the progress bar, the four `-dep-` pill buckets and the
   live dot now paint from the Bridge's own `--s-<status>` ladder instead of
   a second hard-coded palette that had to be re-authored per theme. Measured
   before removal: 0 remaining users in src/client/*.ts for each of
   .hc-progress*, .hc-dep-*, .hc-pill-dep-*, .hc-live*; .hc-pill (base),
   .hc-btn, .hc-crumb and .hc-header stay because home/users/audit still use
   them (15 / 29 / 1 / 2 uses). */

/* =====================================================================
   design-system-foundation — shared primitives (R34/R35/R36/R58).
   Grid typography, the running glow, and the tooltip layer. The motion
   MACHINE lives in motion.ts; the exit convention it drives is the Helm's:
   enter = [hidden]=false + `open` on a double-rAF; exit = add `leaving`,
   finalize on transitionend with a 400ms fallback. Surfaces author their
   own `.x.leaving` rules INSIDE THEIR OWN section anchor (end of file) and
   register every animated class in THE reduced-motion guard (also end of
   file, one selector per line, alphabetical).
   ===================================================================== */

/* R34 — WaaG's header treatment on the console grids. CASING IS CSS, not
   data: no column renaming on either side. Values get tabular figures so
   columns of numbers align the way WaaG's do. */
.hc-grid .ag-header-cell-text { letter-spacing: .08em; text-transform: uppercase; }
.hc-grid .ag-cell { font-variant-numeric: tabular-nums; }

/* Cell-content vertical alignment (owner, 2026-09-04). AG Grid's .ag-cell
   centers TEXT via line-height, so any renderer root TALLER than the type —
   the framework pill, a status/VM chip, the fleet counters, an avatar
   stack — sits on the text baseline and rides high of the row's middle.
   Chip-shaped roots re-anchor with vertical-align:middle; holders carrying
   real buttons ride the .hc-actions flex idiom (the users grid's action
   cell), and the throughput holder stretches to the full row height so its
   waveform can span it (the svg is DRAWN at row height — home.ts — never
   CSS-stretched, which would distort its strokes). */
.hc-grid .ag-cell > .hc-au,
.hc-grid .ag-cell > .hc-fleet,
.hc-grid .ag-cell > .hc-fw,
.hc-grid .ag-cell > .hc-owner,
.hc-grid .ag-cell > .hc-pill,
.hc-grid .ag-cell > .hc-ts { vertical-align: middle; }
.hc-grid .hc-wave { display: flex; height: 100%; }

/* R36 — running indicators GLOW. The Helm's breathe idiom on --hc-glow:
   geometry from the token (zeroed in light mode — ruling Q4 default), color
   from currentColor so the semantic ladder keeps owning the hue. Applies to
   the test-server Live dot and the VM Running dot; both selectors are
   registered in the reduced-motion guard. */
.hc-ts-live .hc-ts-dot,
.hc-vm-running .hc-ts-dot {
  box-shadow: var(--hc-glow) currentColor;
  animation: hc-breathe 1.6s var(--ease-out) infinite;
}
@keyframes hc-breathe { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* The tooltip layer — pi-stack's installTooltips paints into this class
   (mounted once on #app by app.ts). The Helm's .bridge-tip, re-authored in
   the console's --hc-* vocabulary: the console :root has NO floor for the
   Bridge's raw --surface/--ink names, so the borrowed rule would render
   unstyled on a first-ever visit. Rich per-consumer content (rosters, agent
   rows) is styled by the seams that ship it, in their anchors. */
/* The stacking seat (#1129, owner ruling 2026-09-09): tooltips sit BELOW the
   popover tier (.avm-menu 200, dd pops 210/300, .hc-ctxmenu 320) so a tip can
   never paint over the menu its trigger just opened — the backstop under
   tooltip.ts's rule 2 (suppressed while a popover is open). Measured before:
   1000, above every popover; the avatar's email tip sat on the Settings
   header (the owner's screenshot). An anchor INSIDE an overlay is raised to
   that overlay's z + 1 by tooltip.ts at show time. Declared here, beside the
   rule that reads it, not in the token block. */
:root { --z-tooltip: 150; }
.bridge-tip {
  position: fixed; z-index: var(--z-tooltip, 150); pointer-events: none;
  max-width: 360px; padding: 5px 9px; border-radius: var(--radius-sm);
  background: var(--hc-surface); color: var(--hc-fg);
  border: 1px solid var(--hc-border2);
  box-shadow: 0 6px 18px -6px color-mix(in srgb, var(--hc-fg) 35%, transparent);
  font-size: 11.5px; line-height: 1.45;
  /* Multi-line plain tips: the layer sets textContent, and the native
     title= it replaces rendered \n — losing that here would flatten the
     framework column's running/latest/behind ledger into one line
     (framework-column; zero data-tip consumers predate this). */
  white-space: pre-line;
  opacity: 0; transform: translateY(2px);
  transition: opacity var(--t-micro), transform var(--t-micro);
}
.bridge-tip.on { opacity: 1; transform: none; }
/* Rule 1's leave (see the Helm's twin): .leaving is the activation fade +
   scale-out; the reduced-motion guard below already lists .bridge-tip. */
.bridge-tip.leaving { opacity: 0; transform: translateY(2px) scale(.96); }
.bridge-tip.rich { padding: 7px 10px; min-width: 250px; }

/* --- admin pages: User management (#/users) + Audit trail (#/audit) ---
   Split out of the one combined admin screen. Every colour below goes
   through an --hc-* token; this file's rule is that no rule may name a bare
   hex, and these two pages are no exception. */

.hc-adminnav { display: flex; gap: 4px; margin-left: 12px; }
.hc-navlink {
  color: var(--hc-muted); text-decoration: none; font-size: 13px;
  padding: 4px 10px; border-radius: var(--radius-sm);
}
.hc-navlink:hover { color: var(--hc-fg); background: var(--hc-surface2); }
.hc-navlink-on { color: var(--hc-fg); background: var(--hc-surface2); }

.hc-toolbar {
  display: flex; align-items: center; gap: 8px;
  padding: 0 0 12px; flex-wrap: wrap;
}
.hc-spacer { flex: 1; }
/* `.hc-search` is the search INPUT's layout hook (PR-3a): the box it used
   to be is the form-control primitive's field now, and the paint lives in
   the fc-component-block. */
.hc-search { min-width: 0; font-size: 13px; }
/* `.hc-select` is a LAYOUT hook now (PR-3a): the three audit filters it
   dressed were native <select>s, whose open list is OS chrome — they are the
   form-control primitive, and the fc-component-block paints them. */
.hc-select .hc-dd-btn, .hc-select .fc-sel-btn { padding: 6px 10px; font-size: 13px; }
/* `.hc-search:focus`'s hand-picked ring retired (#1262 class B): the input is the fc
   primitive's `.fc-text-input` (outline: 0 in the fc block) and the focus treatment is
   the wrapper's `.fc-field:focus-within` through the --fc-field-focus-* seats. */
.hc-copyfield:focus { outline: 2px solid var(--hc-accent); outline-offset: -1px; }

.hc-btn-primary {
  background: var(--hc-accent); border-color: var(--hc-accent);
  color: var(--hc-bg); font-weight: 600;
}
.hc-btn-danger, .hc-action-danger { color: var(--hc-danger); }

/* Identity: avatar + name + address, one visual unit in both grids. */
.hc-person { display: flex; align-items: center; gap: 10px; height: 100%; }
.hc-person-text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.hc-person-name { color: var(--hc-fg); font-size: 13px; }
.hc-person-mail {
  color: var(--hc-muted); font-size: 11px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-you {
  margin-left: 6px; padding: 0 6px; border-radius: var(--radius-md); font-size: 10px;
  background: var(--hc-surface2); color: var(--hc-muted); text-transform: uppercase;
}
/* THE ONE .hc-avatar SEAT lives in the "avatar" section below (--hc-avatar-size).
   A second .hc-avatar block here was the stretched-avatar defect: this block's
   `flex: 0 0 28px` survived the cascade while the later block's width/height
   won, so every avatar in a flex row painted 28px wide against the seat's
   height — 28×40 in the menu head (.hc-avatar-lg). Grid identity rows keep
   their compact 28px via the size var, not a rival definition. */
.hc-person .hc-avatar, .hc-owner .hc-avatar, .hc-au .hc-avatar { --hc-avatar-size: 28px; }
.hc-avatar-initials {
  font-size: 11px; font-weight: 600; color: var(--hc-muted);
  border: 1px solid var(--hc-line);
}
.hc-avatar-system { color: var(--hc-muted); }

.hc-pill-role-admin         { background: var(--hc-p-violet-bg); color: var(--hc-p-violet-fg); }
.hc-pill-role-release-owner { background: var(--hc-p-blue-bg);   color: var(--hc-p-blue-fg); }
.hc-pill-role-member        { background: var(--hc-p-green-bg);  color: var(--hc-p-green-fg); }
.hc-pill-role-viewer        { background: var(--hc-p-neutral-bg); color: var(--hc-p-neutral-fg); }
.hc-pill-state-active   { background: var(--hc-p-green-bg);   color: var(--hc-p-green-fg); }
.hc-pill-state-invited  { background: var(--hc-p-amber-bg);   color: var(--hc-p-amber-fg); }
.hc-pill-state-disabled { background: var(--hc-p-red-bg);     color: var(--hc-p-red-fg); }

/* Audit action families. Refusals read red on purpose: the audit trail's
   most-asked question is "what was denied, and to whom". */
.hc-pill-act-auth    { background: var(--hc-p-cyan-bg);    color: var(--hc-p-cyan-fg); }
.hc-pill-act-deny    { background: var(--hc-p-red-bg);     color: var(--hc-p-red-fg); }
.hc-pill-act-user    { background: var(--hc-p-violet-bg);  color: var(--hc-p-violet-fg); }
.hc-pill-act-release { background: var(--hc-p-blue-bg);    color: var(--hc-p-blue-fg); }
.hc-pill-act-deploy  { background: var(--hc-p-olive-bg);   color: var(--hc-p-olive-fg); }
.hc-pill-act-neutral { background: var(--hc-p-neutral-bg); color: var(--hc-p-neutral-fg); }
.hc-pill-act-auth, .hc-pill-act-deny, .hc-pill-act-user,
.hc-pill-act-release, .hc-pill-act-deploy, .hc-pill-act-neutral {
  font-family: var(--font-accent);
  font-size: 11px; text-transform: none;
}

.hc-time { color: var(--hc-fg); }
.hc-actions { display: flex; align-items: center; height: 100%; }
.hc-subject { display: flex; align-items: baseline; gap: 6px; }
.hc-subject-type { color: var(--hc-fg); font-size: 12px; }
.hc-subject-id {
  color: var(--hc-muted); font-size: 11px;
  font-family: var(--font-data);
}
.hc-detailcell { display: flex; align-items: center; gap: 8px; height: 100%; }
.hc-detail-summary {
  color: var(--hc-muted); font-size: 12px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-json {
  background: var(--hc-bg); border: 1px solid var(--hc-line); border-radius: var(--radius-sm);
  padding: 12px; max-height: 320px; overflow: auto; font-size: 12px;
  color: var(--hc-fg); white-space: pre-wrap; word-break: break-word;
}

/* Dialogs: the invite modal and the role picker. */
.hc-form { display: flex; flex-direction: column; gap: 14px; margin-top: 16px; }
.hc-field { display: flex; flex-direction: column; gap: 5px; }
.hc-field-label { font-size: 12px; color: var(--hc-muted); }
/* CHILD combinator on purpose: this rule dresses the field's OWN control.
   As a descendant selector it reached into anything embedded in a field —
   the token dialog's scope checkboxes each got width:100% + text-input
   padding and the scope cards blew up into ~240px slabs with the checkbox
   smeared across them. A field wraps exactly one direct control; lists
   nested inside a field style themselves. */
.hc-field > input, .hc-field > select, .hc-copyfield {
  padding: 8px 10px; border-radius: var(--radius-sm); border: 1px solid var(--hc-line);
  background: var(--hc-surface); color: var(--hc-fg); font-size: 13px; width: 100%;
}
.hc-roleblurb { margin: -6px 0 0; font-size: 12px; color: var(--hc-muted); }
.hc-dialog-msg { color: var(--hc-danger); font-size: 12px; margin: 0; min-height: 16px; }
.hc-rolelist { display: flex; flex-direction: column; gap: 4px; margin: 16px 0; }
.hc-roleopt {
  display: flex; gap: 10px; align-items: flex-start; padding: 10px;
  border: 1px solid var(--hc-line); border-radius: var(--radius-md); cursor: pointer;
}
.hc-roleopt:hover { background: var(--hc-surface2); }
.hc-roleopt-name { display: block; font-size: 13px; color: var(--hc-fg); }
.hc-roleopt-blurb { display: block; font-size: 12px; color: var(--hc-muted); margin-top: 2px; }

/* FW5 (VM-fork) — the checkpoint picker (Fork Release dialog). The list is
 * the role-picker skeleton with the filter list's scroll cap; every color
 * rides a token. */
.hc-fork-dialog { max-width: 560px; }
.hc-fork-status { font-size: 12px; color: var(--hc-muted); margin: 6px 0 0; }
/* The launch preflight seat (create-release modal + fork dialog): one status
   line, a collapsible check list, the "launch anyway" acknowledgement. */
.hc-preflight { margin: 6px 0 0; font-size: 12px; }
.hc-preflight-status { margin: 0; color: var(--hc-muted); min-height: 16px; }
.hc-preflight-status[data-verdict="blocked"] { color: var(--hc-danger); }
.hc-preflight-status[data-verdict="ack"] { color: var(--hc-p-amber-fg); }
.hc-preflight-details { margin: 4px 0 0; min-height: 18px; }
.hc-preflight .hc-off { visibility: hidden; }
.hc-preflight-details > summary { cursor: pointer; color: var(--hc-muted); }
.hc-preflight-list { list-style: none; margin: 4px 0 0; padding: 0; max-height: 180px; overflow-y: auto; }
.hc-preflight-line { padding: 2px 0; border-top: 1px solid var(--hc-border); font-family: var(--font-code); font-size: 11px; white-space: pre-wrap; }
.hc-preflight-fail { color: var(--hc-danger); }
.hc-preflight-warn { color: var(--hc-p-amber-fg); }
.hc-preflight-skip { color: var(--hc-muted); opacity: .7; }
.hc-preflight-remedy { color: var(--hc-muted); padding-left: 1.5em; }
.hc-preflight-ack { min-height: 18px; display: flex; gap: 6px; align-items: flex-start; margin: 6px 0 0; color: var(--hc-p-amber-fg); }
.hc-fork-list {
  display: flex; flex-direction: column; gap: 4px; margin: 12px 0;
  max-height: 260px; overflow-y: auto;
}
.hc-ckpt-off { opacity: 0.55; cursor: not-allowed; }
/* Loading placeholders: the dialog shell renders immediately; these shimmer
 * where the checkpoint rows will land (never a bare "Loading…" modal). */
.hc-ckpt-skel {
  flex: none; height: 42px; border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--hc-surface2) 25%, var(--hc-track) 50%, var(--hc-surface2) 75%);
  background-size: 200% 100%;
  animation: hc-ckpt-shimmer 1.2s ease-in-out infinite;
}
@keyframes hc-ckpt-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}
.hc-ckpt-b {
  display: inline-block; margin-left: 6px; padding: 0 5px; border-radius: 3px;
  font-size: 9.5px; font-weight: 700; letter-spacing: 0.08em; vertical-align: middle;
  background: var(--hc-track); color: var(--hc-ink2);
}
.hc-ckpt-b-pinned { background: var(--hc-p-violet-bg); color: var(--hc-p-violet-fg); }
.hc-ckpt-b-grade { background: var(--hc-p-green-bg); color: var(--hc-p-green-fg); }
.hc-ckpt-b-light { background: var(--hc-p-neutral-bg); color: var(--hc-p-neutral-fg); }
.hc-ckpt-b-partial { background: var(--hc-p-amber-bg); color: var(--hc-p-amber-fg); }
.hc-ckpt-b-full { background: var(--hc-p-cyan-bg); color: var(--hc-p-cyan-fg); }
.hc-fork-contract { margin: 10px 0 0; font-size: 12px; color: var(--hc-ink2); }
.hc-fork-contract summary { cursor: pointer; color: var(--hc-fg); font-size: 12px; }
.hc-fork-contract-head { margin: 8px 0 2px; font-weight: 600; color: var(--hc-fg); }
.hc-fork-contract ul { margin: 0 0 4px; padding-left: 18px; }
.hc-fork-contract li { margin: 2px 0; }

/* The absent-seat notice. Loud on purpose: with no email configured this
   dialog is the ONLY way the invitee ever receives their link. */
.hc-warn {
  display: flex; flex-direction: column; gap: 4px; margin: 16px 0;
  padding: 12px; border-radius: var(--radius-md);
  background: var(--hc-p-amber-bg); color: var(--hc-p-amber-fg);
}
.hc-warn-detail { font-size: 12px; opacity: 0.9; word-break: break-word; }
.hc-copyrow { display: flex; gap: 8px; margin: 12px 0; }
.hc-copyrow .hc-btn { flex: 0 0 auto; }
.hc-copyrow > .fc-field { flex: 1; min-width: 0; } /* the primitive's wrapper fills the row the bare input's width:100% used to (#1658 slice b) */
.hc-dialog h2 { font-size: 17px; margin: 0 0 6px; }
/* =====================================================================
 * HOME-SCREEN CHROME — the release UI's vocabulary, on the console.
 *
 * The owner's requirement was that this screen be consistent with the UI
 * inside a release: same icons, same chrome, same components, same motion.
 * The icons are literally the same sprite (scripts/ensure-icons.mjs copies
 * the Bridge's at prebuild). The motion is the same three durations and
 * three easings, copied by value above. The component shapes below are the
 * Bridge's — .ctrl / .card / .glass menu / .modes segmented toggle — renamed
 * into the console's --hc-* token vocabulary rather than re-tuned, so the two
 * surfaces read as one product.
 *
 * Every rule here goes through a token. A bare hex would render correctly in
 * whichever mode it was written in and silently wrong in the other, which is
 * the exact class of bug the two-layer token system exists to remove.
 * ===================================================================== */

/* ---- shared entrance beats (names + curves match the release UI) ---- */
@keyframes hc-rise-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
@keyframes hc-pop-in {
  from { opacity: 0; transform: translateY(-3px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes hc-card-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* ---- icons ---- */
/* Identical contract to the Bridge's `.icon`: 24-viewBox, pure stroke,
   currentColor, width owned by the consumer. The sprite is injected once at
   boot by app.ts. */
.icon {
  width: 1em; height: 1em; vertical-align: -0.12em;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
  flex: none;
}
/* The sprite host id comes from the Bridge's icons.ts, which is copied here
   verbatim by scripts/ensure-icons.mjs — so the selector is the Bridge's name,
   not a console one. Renaming it here would silently stop matching. */
#bridge-icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ---- header ---- */
.hc-header { gap: 12px; }
.hc-brand { display: inline-flex; align-items: center; gap: 9px; }
.hc-brand .icon {
  width: 22px; height: 22px; color: var(--hc-accent);
  /* The rail's brand mark carries the same accent glow in the release UI. */
  filter: drop-shadow(0 0 6px var(--hc-accent-dim));
}
/* The style's STANDALONE logo + wordmark (public/logo-<style>.svg,
   public/wordmark-<style>.svg — outlines, no font dependency), inlined into
   the two brand hooks by the console's copy of brand.ts under a non-identity
   style (owner ruling 2026-09-08). The logo takes the sprite glyph's 22px box
   (no drop-shadow: style:identity reads a `filter` here as an added
   declaration in the identity rendering, so the glow stays the sprite's);
   the wordmark is a 40-unit-tall outline set to the header's text height,
   width from its viewBox. Under Blueprint the hooks keep the sprite `.icon`
   and the text span — nothing here selects. */
.hc-brand .hc-brand-mark { display: inline-flex; }
.hc-brand .hc-brand-mark > svg:not(.icon) { width: 22px; height: 22px; display: block; color: var(--hc-accent); }
.hc-brand .hc-brand-word > svg { height: 17px; width: auto; display: block; color: var(--hc-fg); }
.hc-spring { flex: 1; }
.hc-appearance, .hc-account { position: relative; display: inline-flex; align-items: center; gap: 4px; }

/* ---- buttons ---- */
.hc-btn {
  display: inline-flex; align-items: center; gap: 7px;
  transition:
    background var(--t-micro) var(--ease-out),
    color var(--t-micro) var(--ease-out),
    border-color var(--t-micro) var(--ease-out),
    transform var(--t-micro) var(--ease-out);
}
/* The house press, same 1px drop the release UI uses on every control. */
.hc-btn:active { transform: translateY(1px); }
.hc-btn:focus-visible, .hc-iconbtn:focus-visible, .hc-avatar-btn:focus-visible {
  outline: 2px solid var(--hc-accent); outline-offset: 2px;
}
.hc-btn-primary {
  background: var(--hc-accent); color: var(--hc-onaccent); border-color: var(--hc-accent);
  font-weight: 600;
}
.hc-btn-primary:hover { filter: brightness(1.08); }

/* >>> ibtn-component-block (Styles × Themes PR-2) >>>
 * THE icon-button primitive's CSS twin. Every button in the product renders
 * as `.ibtn` from pi-stack/src/client/buttons.ts: a square glyph well whose
 * INTENT is the signifier and whose NAME lives in the tooltip (data-tip) and
 * the aria-label — the same string, bound by the primitive.
 *
 * This block is BYTE-IDENTICAL in helm-bridge/src/client/styles.css and
 * helm-console/src/client/styles.css and buttons-check refuses a drift. It
 * consumes ONLY the `--btn-*` ramp names; the ramp VALUES are authored above
 * in each surface's own palette vocabulary (the console's :root carries no
 * floor for the Helm's raw --surface/--ink names — the tooltip's precedent).
 *
 * Disabled and busy are NOT authored per intent. They are DERIVED from
 * whichever ramp the button rides by the one formula below, so a new intent
 * cannot ship without its disabled face and no disabled colour is ever
 * hand-picked.
 *
 * ---- THE SHAPE SEAM (W1-1) ------------------------------------------------
 * The ramp above carries fill / ink / border per intent — COLOUR ONLY. A
 * style could re-colour a box it could not un-draw, which is why every style
 * shipped Blueprint's 7px bordered chip with different paint and why the
 * owner saw a border on Starship where the spike draws a bare glyph. Two
 * seams fix that, and Blueprint declares NEITHER (every new name below is
 * read through a var() fallback that resolves to today's value, so the
 * identity style is a byte-for-byte identity transform):
 *
 *   1. REST CHROME, per intent. `--btn-<intent>-rest-fill` /
 *      `-rest-border` default to that intent's `-fill` / `-border`. A
 *      borderless style zeroes an intent in one declaration per end; a
 *      wholly borderless style zeroes `--btn-rest-fill` / `--btn-rest-border`
 *      once on its own `.ibtn` scope and takes all four intents with it.
 *      `--btn-<intent>-rest-ink` is the paired INK at rest (#3216), default
 *      that intent's `-ink`: an intent whose rest fill a style zeroes can no
 *      longer keep an ink authored for a filled well (additive's onaccent on
 *      a transparent well painted an empty square). Hover, disabled and busy
 *      still read `--btn-ink`.
 *
 *   2. A DECORATION SLOT, and it is a REAL CHILD rather than a pseudo-
 *      element. `::before` is already claimed (Starship's focus bracket) and
 *      `::after` is already claimed (the busy face), so a style could not
 *      draw a hover reticle, a compass ring or a well without erasing a rule
 *      it could not see. `.ibtn-deco` is emitted by the primitive beside the
 *      glyph and paints nothing until a style hands it `--btn-deco*`.
 *
 * The SHAPE is a fourth seam, `--btn-radius` (W2, QUARTERDECK's compass
 * well): the primitive's 7px corner and the size ladder's 6/7/8px are each
 * read through `var(--btn-radius, <that px>)`, so a style that wants a
 * CIRCLE at every rung declares one custom property on its `.ibtn` scope
 * instead of a `border-radius` rule — and a custom property is what the
 * console's generator carries (custom properties only, on bare `.ibtn`),
 * where a `border-radius` declaration was dropped and the well's ring
 * rendered as Blueprint's rounded square on the Bridge home.
 *
 * The hover CHROME is a third fallback pair (`--btn-hover-chrome-*`) so that
 * a style whose decoration REPLACES the box can cancel the hover fill and
 * border without touching `--btn-hover-border`, which the armed ring and the
 * focus outline both read. Mainframe's fill-and-invert is those same two
 * names pointed at each other. */
.ibtn {
  --btn-fill: var(--btn-neutral-fill);
  --btn-ink: var(--btn-neutral-ink);
  --btn-border: var(--btn-neutral-border);
  --btn-hover-fill: var(--btn-neutral-hover-fill);
  --btn-hover-ink: var(--btn-neutral-hover-ink);
  --btn-hover-border: var(--btn-neutral-hover-border);
  --btn-rest-fill: var(--btn-neutral-rest-fill, var(--btn-fill));
  --btn-rest-ink: var(--btn-neutral-rest-ink, var(--btn-ink));
  --btn-rest-border: var(--btn-neutral-rest-border, var(--btn-border));
  --ibtn-size: 34px;
  --ibtn-glyph: 20px;
  height: var(--ibtn-size); width: var(--ibtn-size); min-width: var(--ibtn-size);
  flex: none; padding: 0; border-radius: var(--btn-radius, 7px); cursor: pointer;
  border: 1px solid var(--btn-rest-border);
  background: var(--btn-rest-fill);
  color: var(--btn-rest-ink);
  display: inline-flex; align-items: center; justify-content: center;
  position: relative; z-index: 0;
  font: 600 13px var(--font-ui);
  transition: background var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out),
              border-color var(--t-micro) var(--ease-out), transform var(--t-micro) var(--ease-out);
}
.ibtn-xs { --ibtn-size: 22px; --ibtn-glyph: 14px; border-radius: var(--btn-radius, 6px); }
.ibtn-sm { --ibtn-size: 26px; --ibtn-glyph: 16px; border-radius: var(--btn-radius, 6px); }
.ibtn-md { --ibtn-size: 34px; --ibtn-glyph: 20px; }
.ibtn-lg { --ibtn-size: 40px; --ibtn-glyph: 24px; border-radius: var(--btn-radius, 8px); }
/* `flex: none` is load-bearing, not tidiness: the ring is a flex container with
   a PINNED width, so a glyph without it is a shrinkable flex item and any
   horizontal padding a caller adds crushes the art instead of spacing it.
   Measured 2026-09-09: `.pi-bmbar` (padding 4px 8px in a 26px ring) painted an
   8x13 bookmark — the owner's report — and `.fv-raw-toggle` (padding 0 10px)
   painted a 4x13 scroll-text at 0.15 of its slot. */
.ibtn .ibtn-glyph { width: var(--ibtn-glyph); height: var(--ibtn-glyph); flex: none; }

.ibtn[data-btn="additive"] {
  --btn-fill: var(--btn-additive-fill);
  --btn-ink: var(--btn-additive-ink);
  --btn-border: var(--btn-additive-border);
  --btn-hover-fill: var(--btn-additive-hover-fill);
  --btn-hover-ink: var(--btn-additive-hover-ink);
  --btn-hover-border: var(--btn-additive-hover-border);
  --btn-rest-fill: var(--btn-additive-rest-fill, var(--btn-fill));
  --btn-rest-ink: var(--btn-additive-rest-ink, var(--btn-ink));
  --btn-rest-border: var(--btn-additive-rest-border, var(--btn-border));
}
.ibtn[data-btn="destructive"] {
  --btn-fill: var(--btn-destructive-fill);
  --btn-ink: var(--btn-destructive-ink);
  --btn-border: var(--btn-destructive-border);
  --btn-hover-fill: var(--btn-destructive-hover-fill);
  --btn-hover-ink: var(--btn-destructive-hover-ink);
  --btn-hover-border: var(--btn-destructive-hover-border);
  --btn-rest-fill: var(--btn-destructive-rest-fill, var(--btn-fill));
  --btn-rest-ink: var(--btn-destructive-rest-ink, var(--btn-ink));
  --btn-rest-border: var(--btn-destructive-rest-border, var(--btn-border));
}
.ibtn[data-btn="error"] {
  --btn-fill: var(--btn-error-fill);
  --btn-ink: var(--btn-error-ink);
  --btn-border: var(--btn-error-border);
  --btn-hover-fill: var(--btn-error-hover-fill);
  --btn-hover-ink: var(--btn-error-hover-ink);
  --btn-hover-border: var(--btn-error-hover-border);
  --btn-rest-fill: var(--btn-error-rest-fill, var(--btn-fill));
  --btn-rest-ink: var(--btn-error-rest-ink, var(--btn-ink));
  --btn-rest-border: var(--btn-error-rest-border, var(--btn-border));
}

.ibtn:hover:not(:disabled) { background: var(--btn-hover-chrome-fill, var(--btn-hover-fill)); color: var(--btn-hover-chrome-ink, var(--btn-hover-ink)); border-color: var(--btn-hover-chrome-border, var(--btn-hover-border)); }
.ibtn:active:not(:disabled) { transform: translateY(1px) scale(.98); }
.ibtn:focus-visible { outline: 2px solid var(--btn-hover-border); outline-offset: 2px; }

/* THE DECORATION SLOT. A style claims it with ONE declaration; Blueprint
   declares none, so `--btn-deco` is `none`, `--btn-deco-shadow` is `none`
   and the layer is `opacity: 0` in every state — it paints nothing and costs
   no compositing layer. `inset` is animated as well as `opacity` because the
   reticle models (Starship's corner brackets, Destroyer's lock) SNAP inward
   on press, which is a geometry change, not a fade.
   IT PAINTS BETWEEN THE CHROME AND THE GLYPH (W2): `.ibtn` is its own
   stacking context (`z-index: 0` — paint-order identical to `auto` for the
   button itself) and the slot sits at `z-index: -1` inside it, above the
   button's own fill and border and below its in-flow glyph. A fill-type
   model — an invert's deeper press face, a tint well — deepens the box
   without washing the glyph it is there to keep legible (measured before
   this: a 22% press wash over fill AND glyph took Mainframe's destructive
   pair from 4.96 to 3.37); a reticle or a ring is unaffected. */
.ibtn-deco {
  position: absolute; inset: var(--btn-deco-inset, 0); z-index: -1;
  pointer-events: none; opacity: 0; border-radius: inherit;
  background: var(--btn-deco, none);
  box-shadow: var(--btn-deco-shadow, none);
  transition: opacity var(--t-micro) var(--ease-out), inset var(--t-micro) var(--ease-out);
}
.ibtn:hover:not(:disabled) .ibtn-deco {
  opacity: 1;
  background: var(--btn-deco-hover, var(--btn-deco, none));
  inset: var(--btn-deco-hover-inset, var(--btn-deco-inset, 0));
}
.ibtn:active:not(:disabled) .ibtn-deco { inset: var(--btn-deco-active-inset, var(--btn-deco-hover-inset, var(--btn-deco-inset, 0))); }
/* Disabled and busy are DERIVED here too: a decoration is an affordance, and
   an affordance on a button that cannot be pressed is a lie. */
.ibtn:disabled .ibtn-deco, .ibtn[data-btn-state="disabled"] .ibtn-deco,
.ibtn[data-btn-state="busy"] .ibtn-deco, .ibtn.working .ibtn-deco { opacity: 0; }

/* THE TRAVELLING LOCK (W2, owner ruling 2026-09-08). ONE overlay element the
   target-lock runtime (helm-bridge/src/client/target-lock.ts; the Bridge
   home runs a generated copy) positions over the hovered `.ibtn` of a style
   that declares `--btn-deco-travel: 1`, and MOVES to the next hovered
   neighbour — a transition on transform/width/height, not a fade-in per
   button. It wears the target's own `--btn-deco-hover` (copied inline,
   already var()-resolved) so it IS that style's reticle, and the per-button
   slot above is switched off inline for the button it stands on. Under any
   reduce lane the runtime is inert and this element is never shown: the
   static slot is the fallback, and the guard below strikes the travel.
   z-index 1000 = the top of the app's own ladder (the smoke holds every
   layer but the onboarding scrim/layer at 1200/1201 to <= 1000), so the
   onboarding scrim dims a standing lock exactly as it dims the button. */
.ibtn-lock {
  position: fixed; left: 0; top: 0; width: 0; height: 0; z-index: 1000;
  pointer-events: none; opacity: 0;
  background: var(--btn-deco-hover, none);
  box-shadow: var(--btn-deco-shadow, none);
  transition: transform var(--t-micro) var(--ease-out), width var(--t-micro) var(--ease-out),
              height var(--t-micro) var(--ease-out), opacity var(--t-micro) var(--ease-out);
}
.ibtn-lock[data-on="1"] { opacity: 1; }

/* THE derivation — one formula, intent-agnostic. */
.ibtn[data-btn-state="disabled"], .ibtn[data-btn-state="busy"], .ibtn.working {
  background: color-mix(in srgb, var(--btn-rest-fill) 38%, transparent);
  border-color: color-mix(in srgb, var(--btn-rest-border) 38%, transparent);
  color: color-mix(in srgb, var(--btn-ink) 42%, transparent);
  cursor: not-allowed; transform: none;
}
/* `.working` is withPending()'s async-pending class (BR-D1, "no dead
   clicks"). It maps onto the busy face here so the hundreds of existing
   withPending(btn, …) call sites keep working unchanged. */
.ibtn[data-btn-state="busy"], .ibtn.working { cursor: progress; }
.ibtn[data-btn-state="busy"] .ibtn-glyph, .ibtn.working .ibtn-glyph { opacity: 0; }
/* THE BUSY SEAM (#1644). The in-flight face is still this one `::after` —
   the pseudo-element stays OCCUPIED (a style that draws a busy face draws
   it here; `::before` is Starship's focus bracket, and the decoration slot
   is a real child for exactly that reason) — but every value a style used
   to have to override wholesale is read through a `--btn-busy-*` token whose
   var() fallback is today's value, so Blueprint declares NONE of them and
   the resolved output is byte-identical:
     --btn-busy-size           14px             width and height of the face
     --btn-busy-inset          calc(50% - 7px)  left/top offset (a size change
                                                re-centres through this seat)
     --btn-busy-fill           none             background — a plate, needle
                                                or mask paint of the style's own
     --btn-busy-ink            var(--btn-ink)   the ring: 25% track, full head;
                                                `transparent` un-draws the ring
                                                for a filled shape
     --btn-busy-shape          50%              border-radius
     --btn-busy-anim           ibtn-spin        the keyframe name
     --btn-busy-anim-duration  .7s              the period
   The reduced-motion register below keeps its LITERAL 2.4s on purpose: a
   busy indicator that stops moving reads as a hang, so the clamp slows the
   face rather than freezing it, and a style's --btn-busy-anim-duration
   cannot defeat it. `.ibtn.working` (withPending()'s class) rides this same
   rule; the legacy `.ctrl.working::after` outside this fence is untouched. */
.ibtn[data-btn-state="busy"]::after, .ibtn.working::after {
  content: ""; position: absolute;
  width: var(--btn-busy-size, 14px); height: var(--btn-busy-size, 14px);
  left: var(--btn-busy-inset, calc(50% - 7px)); top: var(--btn-busy-inset, calc(50% - 7px));
  border-radius: var(--btn-busy-shape, 50%);
  background: var(--btn-busy-fill, none);
  border: 2px solid color-mix(in srgb, var(--btn-busy-ink, var(--btn-ink)) 25%, transparent);
  border-top-color: var(--btn-busy-ink, var(--btn-ink));
  animation: var(--btn-busy-anim, ibtn-spin) var(--btn-busy-anim-duration, .7s) linear infinite;
}
@keyframes ibtn-spin { to { transform: rotate(360deg); } }

/* ARMED — the ONE confirm-destructive visual. The first click swaps the glyph
   to a check and re-labels the tooltip with the question; this holds the hot
   face so the confirming click lands on a button that already looks
   committed. */
.ibtn[data-btn-armed] {
  background: var(--btn-hover-fill); border-color: var(--btn-hover-border); color: var(--btn-hover-ink);
  animation: ibtn-arm 1.1s var(--ease-out) infinite;
}
@keyframes ibtn-arm {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--btn-border) 55%, transparent); }
  50% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--btn-border) 0%, transparent); }
}
/* One selector per line, byte-wise alphabetical — the shape motion-coverage
   enforces so that every seam appending to this shared register gets a
   distinct insertion offset (two clean merges once silently reverted an
   appended block here). */
@media (prefers-reduced-motion: reduce) {
  .ibtn { transition: none; }
  .ibtn-deco { transition: none; }
  .ibtn-lock { transition: none; }
  .ibtn.working::after { animation-duration: 2.4s; }
  .ibtn[data-btn-armed] { animation: none; box-shadow: 0 0 0 2px var(--btn-hover-border); }
  .ibtn[data-btn-state="busy"]::after { animation-duration: 2.4s; }
}

/* <<< ibtn-component-block <<< */

/* >>> fc-component-block (Styles × Themes PR-3a) >>>
 * THE form-control primitives' CSS twin. Every dropdown, numeric stepper,
 * toggle, text/search field, typeahead combobox and date/time field renders
 * from pi-stack/src/client/form-controls.ts wearing these classes. Native
 * <select>, native number spinners and native checkboxes are banned outright
 * and primitives-check refuses them by file:line: their open list, their
 * arrows and their box are OS chrome, and OS chrome is the one thing a style
 * axis cannot reach.
 *
 * This block is BYTE-IDENTICAL in helm-bridge/src/client/styles.css and
 * helm-console/src/client/styles.css and primitives-check refuses a drift.
 * It consumes ONLY the `--fc-*` field tokens; the token VALUES are authored
 * above in each surface's own palette vocabulary (the `.ibtn` precedent —
 * the console's :root carries no floor for the Helm's raw --surface/--ink).
 *
 * Legacy skin classes (.hc-dd*, .gl-dd*, .hc-searchbox, .hc-ta-*) survive as
 * LAYOUT hooks only: the primitive stamps them BESIDE the fc-* classes so
 * every existing selector, suite and e2e spec keeps addressing the same
 * node, while the paint has exactly one home — this block. primitives-check
 * has the same layout-only arm buttons-check does and refuses a skin class
 * that still carries background / color / border. */
.fc { position: relative; display: inline-flex; align-items: center; min-width: 0; }
.fc-field {
  --fc-h: 34px;
  height: var(--fc-h); box-sizing: border-box;
  border: 1px solid var(--fc-field-border);
  background: var(--fc-field-bg);
  color: var(--fc-field-ink);
  border-radius: 7px;
  font: 500 13px var(--font-ui);
  transition: border-color var(--t-micro) var(--ease-out), box-shadow var(--t-micro) var(--ease-out);
}
.fc-sm .fc-field, .fc-field.fc-sm { --fc-h: 28px; border-radius: var(--radius-sm); font-size: 12px; }
.fc-field:hover:not(.fc-off) { border-color: var(--fc-field-hover-border); }
.fc-field:focus-within {
  border-color: var(--fc-field-focus-border);
  box-shadow: 0 0 0 3px var(--fc-field-focus-ring);
}
/* ONE focus ring per field (#3260). The wrapper above IS the field's focus
   ring; the input nested inside it paints none of its own. Without this, every
   style arm's BLANKET focus treatment — `:root[data-style="…"] :focus-visible`
   (0,3,0), and Studio's `… input:focus-visible` (0,3,1) — outranks
   `.fc-text-input { outline: 0 }` (0,1,0) and paints a SECOND, smaller ring on
   the input inside the wrapper's: measured on the KB portal's header search in
   14 of 16 style x mode cells, and on every text / search / combobox / stepper
   / date-time field in 112 of 128 (Blueprint alone read 1, because its bare
   `:focus-visible` is (0,1,0) and loses). Same mechanism, same remedy as the
   composer-block exemption: written ONCE here at (0,4,0), above every blanket
   arm present and future, never as a per-style patch a new style forgets.
   The outline is TRANSPARENT rather than none: the wrapper's ring is a
   box-shadow, and forced-colors mode draws no box-shadow but does force an
   outline's colour — so High Contrast keeps a visible indicator. */
:root .fc-field .fc-text-input:focus-visible,
:root .fc-field .fc-step-input:focus-visible,
:root .fc-field .fc-dt-seg:focus-visible {
  outline: 2px solid transparent;
  box-shadow: none;
}
.fc-off, .fc-off .fc-field { color: var(--fc-field-disabled-ink); cursor: not-allowed; }

/* ---- 1. select: a trigger plus a listbox, never the OS popup ---------- */
.fc-sel { display: inline-flex; }
.fc-sel-btn {
  display: inline-flex; align-items: center; gap: 8px; justify-content: space-between;
  width: 100%; min-width: 0; padding: 0 8px 0 10px; cursor: pointer;
  font: inherit; color: inherit; text-align: left;
}
.fc-sel-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc-sel-chev {
  width: 16px; height: 16px; flex: none; opacity: .72;
  transition: transform var(--t-micro) var(--ease-out);
}
.fc-sel-btn[aria-expanded="true"] .fc-sel-chev { transform: rotate(180deg); }
.fc-sel-btn:focus-visible { outline: 2px solid var(--fc-field-focus-border); outline-offset: 2px; }

/* ---- the shared popover (select, combobox) ---------------------------- */
.fc-pop {
  position: absolute; z-index: 60; top: calc(100% + 4px); left: 0; min-width: 100%;
  max-height: 280px; overflow-y: auto; padding: 4px;
  border: 1px solid var(--fc-pop-border); border-radius: var(--radius-md);
  background: var(--fc-pop-bg); box-shadow: var(--fc-pop-shadow);
  /* Refraction is a SEAT, not a skin's own paint (#1262 class D): a host that
     wants a glass popover declares --fc-pop-blur on its wrapper; Blueprint
     declares nothing and the fallback is the initial value. */
  backdrop-filter: var(--fc-pop-blur, none);
  animation: fc-pop-in var(--t-micro) var(--ease-out);
}
.fc-pop[hidden] { display: none; }
.fc-pop-portal { position: fixed; top: 0; left: 0; }
@keyframes fc-pop-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
.fc-opt {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 6px 9px; border: 0; border-radius: var(--radius-sm); cursor: pointer;
  background: transparent; color: var(--fc-field-ink);
  font: 500 13px var(--font-ui); text-align: left;
}
.fc-opt:hover:not(:disabled), .fc-opt-active { background: var(--fc-opt-hover-bg); }
.fc-opt:focus-visible { outline: 2px solid var(--fc-field-focus-border); outline-offset: -2px; }
.fc-opt.on { background: var(--fc-opt-on-bg); color: var(--fc-opt-on-ink); }
.fc-opt-off, .fc-opt:disabled { opacity: .45; cursor: not-allowed; }
.fc-opt-ico { width: 14px; height: 14px; flex: none; opacity: .8; }
.fc-opt-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fc-opt-hint { margin-left: auto; color: var(--fc-field-muted); font: 500 11px var(--font-data); }
.fc-opt-more { padding: 6px 9px; color: var(--fc-field-muted); font: 500 11px var(--font-ui); }

/* ---- 2. numeric stepper: the OS spinner arrows never render ----------- */
.fc-step { display: inline-flex; align-items: stretch; overflow: hidden; }
.fc-step-input {
  width: 100%; min-width: 0; padding: 0 6px; border: 0; outline: 0;
  background: transparent; color: inherit;
  /* The FACE comes from the data role; the figure style is a separate
     longhand on purpose — folding it into the `font` shorthand puts a
     keyword in the family slot AND resets font-variant-numeric. */
  font: 600 13px var(--font-data); font-variant-numeric: var(--font-data-numeric);
  text-align: center;
}
.fc-step-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; flex: none; padding: 0; border: 0; cursor: pointer;
  background: transparent; color: var(--fc-field-muted);
  transition: color var(--t-micro) var(--ease-out), background var(--t-micro) var(--ease-out);
}
.fc-step-btn:hover:not(:disabled) { background: var(--fc-opt-hover-bg); color: var(--fc-field-ink); }
.fc-step-btn:disabled { opacity: .4; cursor: not-allowed; }
.fc-step-glyph { width: 12px; height: 12px; }
.fc-step-unit { padding-right: 8px; color: var(--fc-field-muted); font: 500 11px var(--font-ui); }

/* ---- 3. toggle: the ONE binary control (role=switch) ------------------ */
.fc-tg {
  display: inline-flex; align-items: center; gap: 9px; padding: 2px 0;
  border: 0; background: transparent; color: var(--fc-field-ink);
  font: 500 13px var(--font-ui); text-align: left; cursor: pointer;
}
.fc-tg-track {
  position: relative; width: 34px; height: 19px; flex: none; border-radius: var(--radius-pill);
  background: var(--fc-track-off);
  transition: background var(--t-micro) var(--ease-out);
}
.fc-tg-knob {
  position: absolute; top: 2px; left: 2px; width: 15px; height: 15px; border-radius: var(--radius-full);
  background: var(--fc-knob);
  transition: transform var(--t-micro) var(--ease-out), background var(--t-micro) var(--ease-out);
}
.fc-tg[aria-checked="true"] .fc-tg-track { background: var(--fc-track-on); }
.fc-tg[aria-checked="true"] .fc-tg-knob { transform: translateX(15px); background: var(--fc-knob-on); }
.fc-tg:focus-visible { outline: 2px solid var(--fc-field-focus-border); outline-offset: 3px; border-radius: var(--radius-sm); }
.fc-tg:disabled { opacity: .5; cursor: not-allowed; }
.fc-tg-text { min-width: 0; }

/* ---- 4. text / search, with the in-field validity seat ---------------- */
.fc-text { display: inline-flex; align-items: center; gap: 6px; padding: 0 8px; }
.fc-text-input {
  flex: 1; min-width: 0; height: 100%; padding: 0; border: 0; outline: 0;
  background: transparent; color: inherit; font: 500 13px var(--font-ui);
}
.fc-text-input::placeholder { color: var(--fc-field-muted); }
.fc-text-ico { width: 14px; height: 14px; flex: none; opacity: .7; }
.fc-search .fc-clear { margin-right: -2px; }
.fc-search:not(.fc-has) .fc-clear { display: none; }
.fc-vicon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; flex: none;
}
.fc-vicon-glyph { width: 14px; height: 14px; }
.fc-vicon-ok { color: var(--fc-ok); }
.fc-vicon-bad { color: var(--fc-bad); }
.fc-vicon-in { animation: fc-vicon-in var(--t-micro) var(--ease-spring); }
.fc-vicon-out { animation: fc-vicon-out var(--t-micro) var(--ease-out) forwards; }
@keyframes fc-vicon-in { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: none; } }
@keyframes fc-vicon-out { from { opacity: 1; } to { opacity: 0; transform: scale(.6); } }
.fc-live {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- 5. combobox: the typeahead lane over the text field -------------- */
.fc-combo { width: 100%; }
.fc-combo-pop { max-height: 240px; }

/* ---- 6. date / time: segmented entry, no OS picker -------------------- */
.fc-dt { display: inline-flex; align-items: center; gap: 2px; padding: 0 8px; }
.fc-dt-ico { width: 14px; height: 14px; flex: none; margin-right: 4px; opacity: .7; }
.fc-dt-seg {
  min-width: 0; padding: 0; border: 0; outline: 0; background: transparent; color: inherit;
  /* The FACE comes from the data role; the figure style is a separate
     longhand on purpose — folding it into the `font` shorthand puts a
     keyword in the family slot AND resets font-variant-numeric. */
  font: 600 13px var(--font-data); font-variant-numeric: var(--font-data-numeric);
  text-align: center;
}
.fc-dt-Y { width: 4ch; }
.fc-dt-D, .fc-dt-M, .fc-dt-h, .fc-dt-m { width: 2.2ch; }
.fc-dt-seg::placeholder { color: var(--fc-field-muted); }
.fc-dt-seg:focus { background: var(--fc-opt-on-bg); border-radius: 3px; }
.fc-dt-sep { color: var(--fc-field-muted); }

/* One selector per line, byte-wise alphabetical — the shape motion-coverage
   enforces so that every seam appending to this shared register gets a
   distinct insertion offset. */
@media (prefers-reduced-motion: reduce) {
  .fc-field { transition: none; }
  .fc-pop { animation: none; }
  .fc-sel-chev { transition: none; }
  .fc-step-btn { transition: none; }
  .fc-tg-knob { transition: none; }
  .fc-tg-track { transition: none; }
  .fc-vicon-in { animation: none; }
  .fc-vicon-out { animation: none; opacity: 0; }
}
/* <<< fc-component-block <<< */
/* >>> feedback-nav-component-block (Styles × Themes PR-3c) >>>
 * THE feedback & navigation primitive family's CSS twin: scrollbar floor,
 * focus ring, tabs, chips/badges/pills, progress/spinner/skeleton, empty
 * states and the split-pane divider — rendered by
 * pi-stack/src/client/feedback-nav.ts.
 *
 * This block is BYTE-IDENTICAL in helm-bridge/src/client/styles.css and
 * helm-console/src/client/styles.css and primitives-nav-check refuses a
 * drift, exactly as buttons-check does for `.ibtn`. It therefore consumes
 * ONLY token names BOTH roots speak: the `--btn-*` intent ramps (PR-3c mints
 * no `--intent-*` seat of its own — chips, badges and pills ride the button
 * ramps), `--font-*`, `--t-*`, `--ease-*`, `--focus-*`, `--sb-*` and the two
 * `--chip-*` seats each root authors in its own vocabulary. */

/* ---- the SCROLLBAR FLOOR ------------------------------------------------
   The ruling bans the browser's default bar on every scrollable surface. A
   rail is the primary answer (rail-registry.ts declares which surface wears
   which variant), but a rail is a per-pane observer and is therefore wrong
   for content that repeats per message or per row — and it can only attach
   to a pane that exists. This floor covers everything, always, in the rail's
   own colours, so nothing anywhere renders an unthemed OS bar. Lowest
   possible specificity: `.agr-pane`'s hide rule wins wherever a rail lands. */
* { scrollbar-width: thin; scrollbar-color: var(--sb-thumb) var(--sb-track); }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: var(--sb-track); border-radius: 5px; }
*::-webkit-scrollbar-thumb { background: var(--sb-thumb); border-radius: 5px; }
*::-webkit-scrollbar-thumb:hover { background: var(--sb-thumb-hover); }
*::-webkit-scrollbar-corner { background: transparent; }

/* ---- the FOCUS RING -----------------------------------------------------
   ONE treatment app-wide (item H). The bare `:focus-visible` rule is the
   floor — lowest specificity, so any component rule still wins — and
   `.focus-ring` is the class every primitive in this file stamps, which is
   how a primitive gets the ring without hand-picking an outline. */
.focus-ring:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: var(--focus-ring-offset); }
.focus-ring:focus:not(:focus-visible) { outline: none; }

/* ---- TABS ---------------------------------------------------------------
   The strip is a real tablist: roles, roving tabindex and arrow keys come
   from wireTablist(); this is only its skin. `.tabset-new` is the new-tab
   affordance and is deliberately NOT a tab. */
.tabset { display: flex; align-items: center; gap: 2px; min-width: 0; }
.tabset.tabset-v { flex-direction: column; align-items: stretch; }
.tabset-tab {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px; flex: none;
  padding: 6px 11px; border-radius: 7px 7px 0 0; position: relative;
  font: 600 12px var(--font-ui); color: var(--btn-neutral-ink);
  transition: color var(--t-micro) var(--ease-out), background var(--t-micro) var(--ease-out);
}
.tabset-tab:hover { background: var(--btn-neutral-hover-fill); color: var(--btn-neutral-hover-ink); }
.tabset-tab[aria-selected="true"] { color: var(--btn-neutral-hover-ink); background: var(--btn-neutral-fill); }
.tabset-tab[aria-selected="true"]::after {
  content: ""; position: absolute; left: 8px; right: 8px; bottom: 0; height: 2px;
  border-radius: 2px 2px 0 0; background: var(--btn-additive-border);
}
.tabset-v .tabset-tab { border-radius: 7px; }
.tabset-v .tabset-tab[aria-selected="true"]::after { left: 0; right: auto; top: 6px; bottom: 6px; width: 2px; height: auto; }
.tabset-new { margin-inline-start: 4px; }

/* ---- CHIPS / BADGES / STATUS PILLS -------------------------------------
   ONE family. `data-chip` names the intent and rides the SAME ramps the
   buttons do; `data-chip-tone` picks wash / fill / outline. A chip with no
   declared intent is `muted` and keeps the legacy status-chip look (the
   `:not([data-chip])` arms below) so the surfaces that already render status
   chips are byte-stable while the primitive lands. */
.chips { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-accent); font-size: .6rem; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase;
  padding: 3px 9px 2px; border-radius: var(--radius-pill); white-space: nowrap;
  color: var(--chip-ink);
  border: 1px solid currentColor;
  background: color-mix(in srgb, currentColor 10%, transparent);
  transition: color var(--t-standard) var(--ease-out), background var(--t-micro) var(--ease-out);
}
.chips .chip { height: 22px; }
.chip:not([data-chip])::before { content: ""; width: 6px; height: 6px; border-radius: var(--radius-full); background: currentColor; }
.chip-sm { font-size: .55rem; padding: 2px 7px 1px; gap: 4px; }
.chip-dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: currentColor; flex: none; }
.chip-glyph { width: 11px; height: 11px; flex: none; }
.chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* The remove affordance re-points the RAMP rather than painting over it —
   an icon button inside a chip has no well of its own, and buttons-check
   is right to refuse a cls: class that sets border/background directly. */
.chip .chip-x { --ibtn-size: 14px; --ibtn-glyph: 9px; --btn-neutral-fill: transparent; --btn-neutral-border: transparent; margin-inline-end: -4px; }
.chip[data-chip="muted"] { color: var(--chip-mute); }
.chip[data-chip="neutral"] { color: var(--btn-neutral-hover-ink); }
.chip[data-chip="additive"] { color: var(--btn-additive-border); }
.chip[data-chip="destructive"] { color: var(--btn-destructive-ink); }
.chip[data-chip="error"] { color: var(--btn-error-ink); }
.chip[data-chip-tone="outline"] { background: transparent; }
.chip[data-chip-tone="solid"][data-chip="additive"] { background: var(--btn-additive-fill); color: var(--btn-additive-ink); border-color: var(--btn-additive-border); }
.chip[data-chip-tone="solid"][data-chip="destructive"] { background: var(--btn-destructive-fill); border-color: var(--btn-destructive-border); }
.chip[data-chip-tone="solid"][data-chip="error"] { background: var(--btn-error-fill); border-color: var(--btn-error-border); }
.chip[data-chip-tone="solid"][data-chip="neutral"] { background: var(--btn-neutral-fill); border-color: var(--btn-neutral-border); }

/* ---- PROGRESS / SPINNER / SKELETON --------------------------------------
   Every animating arm here has a static reduced-motion arm at the foot of
   this block, and primitives-nav-check refuses a new one that does not. */
.prog { position: relative; overflow: hidden; width: 100%; border-radius: var(--radius-pill); background: var(--sb-track); }
.prog-md { height: 6px; }
.prog-sm { height: 4px; }
.prog-fill {
  display: block; height: 100%; border-radius: inherit; width: 0;
  background: var(--btn-additive-fill);
  transition: width var(--t-standard) var(--ease-out);
}
.prog[data-btn="destructive"] .prog-fill { background: var(--btn-destructive-ink); }
.prog[data-btn="error"] .prog-fill { background: var(--btn-error-ink); }
.prog[data-btn="neutral"] .prog-fill { background: var(--btn-neutral-hover-ink); }
.prog-indet .prog-fill { width: 40%; animation: prog-slide 1.15s var(--ease-out) infinite; }
@keyframes prog-slide { 0% { transform: translateX(-110%); } 100% { transform: translateX(310%); } }
.spin {
  display: inline-block; flex: none; border-radius: var(--radius-full);
  border: 2px solid color-mix(in srgb, currentColor 25%, transparent);
  border-top-color: currentColor;
  animation: fn-spin .7s linear infinite;
}
.spin-sm { width: 12px; height: 12px; }
.spin-md { width: 16px; height: 16px; }
.spin-lg { width: 22px; height: 22px; border-width: 3px; }
@keyframes fn-spin { to { transform: rotate(360deg); } }
.skel { display: flex; flex-direction: column; gap: 8px; }
.skel-line {
  display: block; height: 10px; border-radius: 5px;
  background: linear-gradient(90deg, var(--sb-track) 0%, var(--sb-thumb) 50%, var(--sb-track) 100%);
  background-size: 220% 100%;
  animation: skel-shimmer 1.4s linear infinite;
}
@keyframes skel-shimmer { 0% { background-position: 120% 0; } 100% { background-position: -120% 0; } }

/* ---- EMPTY STATES -------------------------------------------------------
   One treatment: a glyph, a display-role headline, a reading-role hint and
   at most one action (an `.ibtn`, so the intent rules still apply). */
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 32px 20px; text-align: center; }
.empty-state-glyph { width: 26px; height: 26px; opacity: .45; }
.empty-state-title { margin: 0; font: 600 13px var(--font-display); color: var(--chip-ink); }
.empty-state-hint { margin: 0; max-width: 44ch; font: 400 12px/1.55 var(--font-reading); color: var(--chip-mute); }
.empty-state-act { margin-top: 4px; }

/* ---- SPLIT PANES --------------------------------------------------------
   role="separator" with a real keyboard contract (arrows, Home/End, Enter to
   reset) — the reason this is a primitive and not another pointerdown
   handler. The hit area is wider than the visible line. */
.splitter { position: relative; flex: none; background: transparent; touch-action: none; }
.splitter-x { width: 7px; cursor: col-resize; align-self: stretch; }
.splitter-y { height: 7px; cursor: row-resize; }
.splitter::before { content: ""; position: absolute; inset: 0; margin: auto; background: var(--sb-track); }
.splitter-x::before { width: 1px; }
.splitter-y::before { height: 1px; }
.splitter:hover::before, .splitter.dragging::before { background: var(--sb-thumb-hover); }
.splitter-x:hover::before, .splitter-x.dragging::before { width: 3px; }
.splitter-y:hover::before, .splitter-y.dragging::before { height: 3px; }

/* One selector per line, byte-wise alphabetical — the shape motion-coverage
   enforces so that every seam appending to this shared register gets a
   distinct insertion offset. */
@media (prefers-reduced-motion: reduce) {
  .chip { transition: none; }
  .prog-fill { transition: none; }
  .prog-indet .prog-fill { animation: none; width: 100%; opacity: .38; }
  .skel-line { animation: none; background: var(--sb-track); }
  .spin { animation-duration: 2.4s; }
  .tabset-tab { transition: none; }
}
/* <<< feedback-nav-component-block <<< */

/* >>> disc-component-block (Styles × Themes — accordion undo, owner 2026-09-07) >>>
 * THE disclosure primitive's CSS twin. Every accordion/twisty in the product
 * renders as `.disc-tog` + `.disc-region` from pi-stack/src/client/
 * disclosure.ts.
 *
 * OWNER RULING (screenshot of the corpus tree): the accordion expand/collapse
 * is NOT a button — "it should just use icons, like what you see by the
 * 'In-Progress' line". So this block deliberately consumes the neutral ramp's
 * INK slots and NOTHING else: no --btn-*-fill, no --btn-*-border, no press
 * transform, no focus fill. The affordance is the ROTATION plus a hover
 * colour. primitives-check's `disclosure` row asserts that absence directly —
 * a fill or border reference reappearing in this block is the regression it
 * refuses, and it is the same regression PR 470 (b6d275835) shipped.
 * (The PR is named WITHOUT a leading # on purpose: this file is scanned for
 * bare hex colours, and a hash followed by 3-8 hex digits reads as one — the
 * comment-blanking twin lives in admin-pages.test.mjs + home-chrome.test.mjs.)
 *
 * This block is BYTE-IDENTICAL in helm-bridge/src/client/styles.css and
 * helm-console/src/client/styles.css (the .ibtn / fc precedent) and
 * primitives-check refuses a drift.
 *
 * THE DRAWER is `grid-template-rows: 0fr -> 1fr`, not a measured max-height:
 * it animates to the CONTENT's own height, so there is no magic number to
 * outgrow and nothing is clipped once it settles (at 1fr the track is
 * auto-sized). `min-height: 0` on the inner element is load-bearing — a grid
 * item's automatic minimum size is its content, which would pin the track
 * open and animate nothing. */
.disc-tog {
  --disc-glyph: 16px;
  flex: none; padding: 0; margin: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; align-self: stretch;
  border: 0; background: none;
  color: var(--btn-neutral-ink);
  transition: color var(--t-micro) var(--ease-out);
  -webkit-appearance: none; appearance: none;
}
.disc-tog-xs { --disc-glyph: 12px; width: 16px; }
.disc-tog-sm { --disc-glyph: 16px; width: 20px; }
.disc-tog-md { --disc-glyph: 18px; width: 24px; }
.disc-tog:hover { color: var(--btn-neutral-hover-ink); }
/* The focus ring is the ONE ring the design system already owns — an outline,
   which paints outside the box and so adds no chrome to the resting state. */
.disc-tog:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; border-radius: var(--radius-xs); }
.disc-tog:disabled { opacity: .25; pointer-events: none; cursor: default; }
.disc-chev {
  width: var(--disc-glyph); height: var(--disc-glyph); flex: none;
  transform: rotate(0deg);
  transition: transform var(--t-standard) var(--ease-out);
}
/* ONE glyph, rotated — never a second sprite id. PR 470 replaced this very
   rotation with a chev/chevron-down symbol swap, which is how the control
   lost its animation; the state drives the TRANSFORM again.
   DIRECT CHILD, deliberately: a disclosure whose whole ROW is the toggle (the
   corpus tree's group header, the glance group row) carries aria-expanded on
   the row rather than on a nested button, and both shapes must rotate off the
   same rule. A descendant combinator would also rotate a CLOSED inner
   chevron sitting inside an OPEN outer region — which is exactly what a
   nested tree is. */
[aria-expanded="true"] > .disc-chev { transform: rotate(90deg); }
/* The row-as-toggle shape: no chrome of its own beyond what the surface's
   own row rule paints, and the same hover ink as the bare toggle. */
.disc-row { cursor: pointer; user-select: none; }
.disc-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; }

.disc-region {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows var(--t-deliberate) var(--ease-out);
}
.disc-region[data-disc-open="1"] { grid-template-rows: 1fr; }
.disc-region > .disc-region-in { overflow: hidden; min-height: 0; }

/* The polite line announceDisclosure() speaks into — visually hidden here
   rather than through a surface-local .sr-only, because this block ships to
   both homes and only one of them authors that utility. */
.disc-live {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* One selector per line, byte-wise alphabetical — the shape motion-coverage
   enforces so that every seam appending to this shared register gets a
   distinct insertion offset. Both reduce lanes collapse the SAME two
   transitions: the state still changes, it just arrives instantly. */
@media (prefers-reduced-motion: reduce) {
  .disc-chev { transition: none; }
  .disc-region { transition: none; }
  .disc-tog { transition: none; }
}
[data-reduce="1"] .disc-chev { transition: none; }
[data-reduce="1"] .disc-region { transition: none; }
[data-reduce="1"] .disc-tog { transition: none; }
/* <<< disc-component-block <<< */

/* ---- the configuration editor's token seat (cfg-component-block below) ----
   CFG_TOKEN_SLOTS (pi-stack/src/client/config-editor.ts), authored in the
   console's --hc-* vocabulary. The Helm authors the SAME slots from its own
   palette; the block that consumes them is byte-identical in both homes. */
:root {
  --cfg-fg: var(--hc-fg);
  --cfg-muted: var(--hc-muted);
  --cfg-panel: var(--hc-panel);
  --cfg-surface: var(--hc-surface);
  --cfg-surface-2: var(--hc-surface2);
  --cfg-line: var(--hc-line);
  --cfg-hairline: var(--hc-hairline);
  --cfg-radius: var(--hc-radius);
  --cfg-accent: var(--hc-accent);
  --cfg-accent-dim: var(--hc-accent-dim);
  --cfg-danger: var(--hc-danger);
  --cfg-badge-info-bg: var(--hc-p-blue-bg);
  --cfg-badge-info-fg: var(--hc-p-blue-fg);
  --cfg-badge-warn-bg: var(--hc-p-amber-bg);
  --cfg-badge-warn-fg: var(--hc-p-amber-fg);
  --cfg-badge-neutral-bg: var(--hc-p-neutral-bg);
  --cfg-badge-neutral-fg: var(--hc-p-neutral-fg);
}

/* >>> cfg-component-block (THE configuration editor — the Helm's settings tab + the Bridge's config templates, owner ruling 2026-09-08) >>>
   ONE editor (pi-stack/src/client/config-editor.ts), two homes. This block is
   byte-identical in helm-bridge/src/client/styles.css and helm-console/src/
   client/styles.css — primitives-check refuses a drift, and a home that leaves
   any --cfg-* slot (CFG_TOKEN_SLOTS) undefined. Every colour rides a --cfg-*
   token the home authors in its own vocabulary just above this block; the
   dropdown, the search box and the toggle are PAINTED by the fc-component-
   block — the cfg-dd* / cfg-search* / cfg-toggle names here are layout hooks.
   Transitions only, no keyframes but the search ladder's two (registered in
   this block's own reduced-motion twin below). */

/* Header trio: the dirty sentence, Discard, Review & save. */
.cfg-head { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; }
.cfg-dirty { color: var(--cfg-muted); font-size: 12.5px; white-space: nowrap; }
.cfg-dirty-bad { color: var(--cfg-danger); }

/* Editor layout: sticky side (search + section nav) beside the card column.
   The page is viewport-fixed; only the card column scrolls. */
.cfg-editor { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 18px; height: 100%; min-height: 0; }
.cfg-side { display: flex; flex-direction: column; gap: 10px; min-height: 0; padding: 14px 0 14px 16px; }
/* The search FITS its 240px column (224px after the side padding) — the old
   raw input's 260px min-width slid under the content pane's cards. */
.cfg-side .cfg-search { flex: none; width: 100%; min-width: 0; }
.cfg-side .cfg-search-input { min-width: 0; }
.cfg-nav { position: relative; display: flex; flex-direction: column; gap: 1px; overflow-y: auto; min-height: 0; padding-right: 4px; }
/* Scroll-spy indicator: ONE absolutely-positioned pill that slides (top/
   height transition) behind the active row — never one highlight per row. */
.cfg-navind { position: absolute; left: 0; right: 4px; border-radius: var(--radius-sm); background: var(--cfg-surface-2); opacity: 0; transition: top var(--t-standard) var(--ease-out), height var(--t-standard) var(--ease-out), opacity var(--t-micro) var(--ease-out); pointer-events: none; }
.cfg-navind.on { opacity: 1; }
.cfg-navrow { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 5px 8px; border-radius: var(--radius-sm); color: var(--cfg-muted); text-decoration: none; font-size: 13px; cursor: pointer; transition: opacity var(--t-standard) var(--ease-out), transform var(--t-standard) var(--ease-out); }
.cfg-navrow:hover { background: var(--cfg-surface-2); color: var(--cfg-fg); }
.cfg-navrow.on { color: var(--cfg-fg); }
.cfg-navmeta { display: inline-flex; gap: 4px; align-items: center; }
.cfg-navshown { color: var(--cfg-muted); font-size: 11px; }
.cfg-navchanged, .cfg-navdirty { min-width: 18px; text-align: center; border-radius: 9px; font-size: 11px; line-height: 18px; padding: 0 5px; }
.cfg-navchanged { background: var(--cfg-badge-info-bg); color: var(--cfg-badge-info-fg); }
.cfg-navdirty { background: var(--cfg-badge-warn-bg); color: var(--cfg-badge-warn-fg); }

/* The content pane rides inside a wrap so the shared element scroll rail
   (pi-stack scroll-rails) has a positioning host; the pane's native bar is
   hidden by the rail's own .agr-pane rule. */
.cfg-contentwrap { position: relative; min-height: 0; min-width: 0; }
.cfg-content { position: relative; height: 100%; overflow-y: auto; min-height: 0; padding: 14px 16px 40px 0; display: flex; flex-direction: column; gap: 14px; }
.cfg-card { flex: none; background: var(--cfg-panel); border: 1px solid var(--cfg-line); border-radius: var(--cfg-radius); overflow: hidden; transition: opacity var(--t-standard) var(--ease-out), transform var(--t-standard) var(--ease-out); }
/* Filter animation: two-phase hide (fade+lift, THEN display:none) and the
   reverse on return — rows, cards and nav rows share the one mechanism.
   The [hidden] arm is explicit because these classes author their own
   display, which beats the UA's [hidden] rule. */
.cfg-out { opacity: 0; transform: translateY(-4px); }
.cfg-card[hidden], .cfg-navrow[hidden], .cfg-row[hidden] { display: none; }
.cfg-cardhead { padding: 12px 16px 10px; border-bottom: 1px solid var(--cfg-hairline); }
.cfg-cardtitle { margin: 0; font-size: 15px; }
/* READING (owner 2026-09-08): a section's description and a seat's help are
   prose ABOUT the product, not control chrome — they ride the style's reader
   font on both hosts; .cfg-defhint ("default: X") is a value and stays. */
.cfg-cardblurb { margin: 2px 0 0; color: var(--cfg-muted); font-family: var(--font-reading); font-size: 12.5px; line-height: 1.5; }
/* Whether this section's values reach a release VM at all. A console-only
   section is the one the operator must not mistake for a launch knob, so it
   is the one that carries a colour. */
.cfg-cardreach { margin: 4px 0 0; font-size: 12px; }
.cfg-reach-release-vm, .cfg-reach-note { color: var(--cfg-muted); }
.cfg-reach-console-only { color: var(--cfg-badge-warn-fg); }
.cfg-empty { flex: none; color: var(--cfg-muted); padding: 24px 16px; }
/* A custom section body (a management surface inside the editor's card). */
.cfg-custom { padding: 10px 16px 14px; }

.cfg-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(180px, 352px); gap: 14px; align-items: start; padding: 10px 16px; border-bottom: 1px solid var(--cfg-hairline); transition: background 120ms ease, opacity var(--t-standard) var(--ease-out), transform var(--t-standard) var(--ease-out); }
.cfg-row:last-child { border-bottom: 0; }
.cfg-row-changed { background: color-mix(in srgb, var(--cfg-accent) 4%, transparent); }
.cfg-row-bad { background: color-mix(in srgb, var(--cfg-danger) 6%, transparent); }
.cfg-rowtext { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cfg-label { font-size: 13.5px; color: var(--cfg-fg); display: inline-flex; align-items: center; gap: 6px; }
.cfg-dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--cfg-accent); flex: none; }
.cfg-help { color: var(--cfg-muted); font-family: var(--font-reading); font-size: 12px; line-height: 1.5; }
.cfg-error { color: var(--cfg-danger); font-size: 12px; }
.cfg-defhint { color: var(--cfg-muted); font-size: 11.5px; display: inline-flex; gap: 8px; align-items: baseline; }
.cfg-ctl { display: flex; justify-content: flex-end; align-items: center; gap: 8px; }

/* Per-setting reset: ONE `.ibtn`, two faces — inert (the derived disabled
   face) at the default, error-ramp when the value differs; the glyph springs
   on the swap. */
.cfg-resetbtn .ibtn-glyph { transition: opacity var(--t-standard) var(--ease-out), transform var(--t-standard) var(--ease-spring); }

/* Custom number stepper: text input + a chevron column; the arrows are a
   BOUNDED gesture (disabled at min/max), ArrowUp/ArrowDown on the input
   ride the same lane. Typed values keep the refuse-don't-clamp posture. */
.cfg-step { display: inline-flex; align-items: stretch; width: 100%; max-width: 320px; }
.cfg-step .cfg-input { border-radius: 6px 0 0 6px; border-right: 0; }
.cfg-stepbtns { display: flex; flex-direction: column; flex: none; border: 1px solid var(--cfg-line); border-radius: 0 6px 6px 0; overflow: hidden; background: var(--cfg-surface); }
.cfg-stepbtn { flex: 1 1 auto; display: flex; align-items: center; justify-content: center; width: 22px; padding: 0; border: 0; background: transparent; color: var(--cfg-muted); cursor: pointer; transition: background var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out); }
.cfg-stepbtn + .cfg-stepbtn { border-top: 1px solid var(--cfg-hairline); }
.cfg-stepbtn:hover:not(:disabled) { background: var(--cfg-surface-2); color: var(--cfg-fg); }
.cfg-stepbtn:disabled { opacity: .4; cursor: default; }
.cfg-stepbtn .icon { width: 16px; height: 16px; }

/* The shared dropdown on the control column — layout hooks only (the fc
   block paints the field, the pop and the options). */
.cfg-dd { position: relative; display: inline-flex; }
.cfg-ctl .cfg-dd { width: 100%; max-width: 320px; }
.cfg-ctl .cfg-dd:has(.cfg-dd-btn:disabled) { --fc-field-hover-border: var(--cfg-line); }
.cfg-ctl .cfg-dd-btn:disabled { cursor: default; }
.cfg-dd-btn { gap: 8px; width: 100%; padding: 6px 8px 6px 10px; font-size: 12.5px; transition: border-color var(--t-micro) var(--ease-out), transform var(--t-micro) var(--ease-out); }
.cfg-dd-btn:active { transform: translateY(1px); }
.cfg-dd-label { flex: 1 1 auto; min-width: 0; display: inline-flex; align-items: center; gap: 8px; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cfg-dd-chev { width: 16px; height: 16px; flex: none; transition: transform var(--t-micro) var(--ease-out); }
.cfg-dd-pop { z-index: 210; width: max-content; max-height: 320px; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.cfg-dd-item { gap: 8px; padding: 6px 10px; font-size: 12.5px; white-space: nowrap; }

.cfg-input { width: 100%; max-width: 320px; background: var(--cfg-surface); color: var(--cfg-fg); border: 1px solid var(--cfg-line); border-radius: var(--radius-sm); padding: 5px 8px; font: inherit; font-size: 13px; }
.cfg-input:focus, .cfg-json:focus { outline: 1px solid var(--cfg-accent); border-color: var(--cfg-accent); }
.cfg-json { width: 100%; background: var(--cfg-surface); color: var(--cfg-fg); border: 1px solid var(--cfg-line); border-radius: var(--radius-sm); padding: 6px 8px; font-family: var(--font-code); font-size: 12px; resize: vertical; min-height: 56px; }
.cfg-input:disabled, .cfg-json:disabled { opacity: 0.6; }

/* The toggle primitive on a row: the fc block draws the track and the knob;
   the two colour choices this editor makes (accent-dim ground, accent knob)
   survive as a RE-POINT of the field tokens. */
.cfg-toggle { --fc-track-on: var(--cfg-accent-dim); --fc-knob-on: var(--cfg-accent); }

/* The search box's interaction ladder: inactive -> hover (icon lift) ->
   focus (the fc field wears the ring) -> typing (icon tick per keystroke
   burst, transient by construction) -> has-text (the clear button EXISTS
   only now, entering on a spring). */
.cfg-search { position: relative; display: inline-flex; align-items: center; gap: 7px; padding: 0 6px 0 9px; transition: border-color var(--t-micro) var(--ease-out), box-shadow var(--t-standard) var(--ease-out); }
.cfg-search [hidden] { display: none; }
.cfg-search .cfg-search-input { min-width: 0; flex: 1; padding: 6px 0; }
/* inner-outline suppression retired to `.fc-text-input { outline: 0 }` in the fc block (#1262 class B) */
.cfg-search .cfg-search-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.cfg-sb-ico { width: 15px; height: 15px; flex: none; transition: transform var(--t-micro) var(--ease-out), color var(--t-micro); }
.cfg-search:hover .cfg-sb-ico, .cfg-search:focus-within .cfg-sb-ico { transform: scale(1.12); }
.cfg-search.cfg-sb-typing .cfg-sb-ico { animation: cfg-sb-tick var(--t-standard) var(--ease-out); }
@keyframes cfg-sb-tick { 0% { transform: scale(1.12); } 50% { transform: scale(.9) rotate(-8deg); } 100% { transform: scale(1.12); } }
.cfg-sb-clear { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; padding: 0; border-radius: var(--radius-full); cursor: pointer; flex: none; animation: cfg-sb-clear-in var(--t-standard) var(--ease-spring); transition: transform var(--t-micro) var(--ease-out), color var(--t-micro), background var(--t-micro); }
.cfg-sb-clear:hover { transform: scale(1.1); }
.cfg-sb-clear:active { transform: scale(.85); }
@keyframes cfg-sb-clear-in { 0% { transform: scale(.4); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }

/* Review & save: the diff the audit trail will record. */
.cfg-review-blurb { color: var(--cfg-muted); font-size: 12.5px; margin: 0 0 4px; }
.cfg-review { display: flex; flex-direction: column; gap: 6px; max-height: 320px; overflow-y: auto; margin: 10px 0; }
.cfg-revrow { flex: none; display: flex; flex-direction: column; gap: 2px; padding: 7px 10px; background: var(--cfg-surface); border: 1px solid var(--cfg-hairline); border-radius: var(--radius-sm); }
.cfg-revwhere { color: var(--cfg-muted); font-size: 11.5px; }
.cfg-revvals { display: inline-flex; gap: 8px; align-items: baseline; font-size: 13px; flex-wrap: wrap; }
.cfg-revold { color: var(--cfg-danger); text-decoration: line-through; }
.cfg-revarrow { color: var(--cfg-muted); }
.cfg-revnew { color: var(--cfg-fg); text-decoration: none; font-weight: 600; }
.cfg-review-msg { color: var(--cfg-danger); font-size: 12.5px; min-height: 1em; margin: 4px 0 0; }
.cfg-review-msg:empty { display: none; }

/* Phone: the 240px sidebar + control column stack to one column so neither
   collapses nor overflows. */
@media (max-width: 640px) {
  .cfg-editor { grid-template-columns: 1fr; height: auto; }
  .cfg-row { grid-template-columns: 1fr; }
}

/* Every motion above collapses to instant under reduced motion — this
   block's own twin, so the block stays self-contained across both homes. */
@media (prefers-reduced-motion: reduce) {
  .cfg-card,
  .cfg-dd-btn,
  .cfg-dd-chev,
  .cfg-navind,
  .cfg-navrow,
  .cfg-resetbtn .ibtn-glyph,
  .cfg-row,
  .cfg-sb-clear,
  .cfg-sb-ico,
  .cfg-search,
  .cfg-search.cfg-sb-typing .cfg-sb-ico,
  .cfg-stepbtn { animation: none; transition: none; }
}
/* <<< cfg-component-block <<< */

/* >>> z-ladder-block (the popover family's stacking TOKENS — owner defect 2026-09-09, #1125) >>>
   Stacking is a token, never a literal. --z-popover is the layer an OPEN
   popover (the avatar menu, portaled to <body>) paints on; --z-popover-list
   is the option list a popover opens (one step up, so it paints over the
   popover that owns it). Both sit above every in-content layer — AG Grid's
   own stacking, the synthetic scroll rails (scroll-rails.ts, z-index 6), the
   card popovers at 40 — and below the modal/tooltip tier (1000) and the
   onboarding scrim (1200). Identical in both stylesheet homes. */
:root { --z-popover: 200; --z-popover-list: 210; }
/* <<< z-ladder-block <<< */

/* >>> avm-component-block (THE avatar menu — the Bridge, the Helm, the KB and API portals; owner ruling 2026-09-08) >>>
   ONE menu (pi-stack/src/client/avatar-menu.ts): the profile circle opens the
   appearance section (Dark/Light, the mode's theme, the style), then API
   tokens, then Log out when a session exists. This block is byte-identical in
   helm-bridge/src/client/styles.css and helm-console/src/client/styles.css
   (the portals load the Helm's sheet) — primitives-check refuses a drift. It
   rides the --cfg-* token seat (authored per home above the cfg block) and
   the --mat-popover-* material tier; the dropdown is PAINTED by the
   fc-component-block (avm-dd* are layout hooks). Placement is the host's:
   .avm-place-below (a header: down-and-left) or .avm-place-side (the Helm's
   rail: up-and-right off a bottom anchor). */
.avm { position: relative; display: inline-flex; align-items: center; }
/* THE TRIGGER IS THE PRIMITIVE (#1155). `.avm-btn` used to paint its own box:
   `border: 1px solid transparent`, `background: none`, `border-radius: 50%`,
   a `scale(1.06)` hover, a `scale(.97)` press and an accent focus outline —
   plus a hand-cut `44x40 / radius 12px` rail tile with its own hover fill. So
   the ONE control on those headers that did not follow the style was the
   account circle: measured on origin/main 29327b16, style-parity refused
   `#railAcctBtn` (helm-nav) and `#avmBtn` (kb-article) on ALL EIGHT styles,
   naming what `.ibtn` paints in the same root and it did not.
   The primitive now owns the box, border, fill, hover/press chrome, the
   `.ibtn-deco` slot and the focus ring; avatar-menu.ts writes the SIZE CLASS
   (`ibtn-md` in a header, `ibtn-lg` in the rail) so the avatar circle inside
   keeps exactly the size it has today. Nothing here re-declares chrome — a
   later rule winning over the ramp is its own parity refusal. */
/* THE avatar seat: one size custom property drives width, height AND the
   flex-basis pin together, so a size variant can never desynchronize the axes. */
.avm-avatar { --avm-avatar-size: 30px; width: var(--avm-avatar-size); height: var(--avm-avatar-size); flex: 0 0 var(--avm-avatar-size); aspect-ratio: 1 / 1; border-radius: var(--radius-full); display: inline-grid; place-items: center; overflow: hidden; background: var(--cfg-surface-2); color: var(--cfg-fg); border: 1px solid var(--cfg-line); font-size: 11.5px; font-weight: 700; letter-spacing: .02em; user-select: none; }
.avm-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avm-avatar .icon { width: 20px; height: 20px; color: var(--cfg-muted); }
.avm-avatar-lg { --avm-avatar-size: 40px; font-size: 14px; }
.avm-place-side .avm-avatar { --avm-avatar-size: 34px; font-size: 12px; }
.avm-place-side .avm-avatar-lg { --avm-avatar-size: 40px; font-size: 14px; }

.avm-menu { position: absolute; z-index: var(--z-popover, 200); min-width: 232px; padding: 7px 7px 9px; background: var(--mat-popover-bg, var(--cfg-panel)); border: 1px solid var(--mat-popover-brd, var(--cfg-line)); backdrop-filter: var(--mat-popover-blur, none); -webkit-backdrop-filter: var(--mat-popover-blur, none); border-radius: 13px; box-shadow: var(--mat-popover-sh, 0 16px 40px rgba(0, 0, 0, .35)), inset 0 1px 0 var(--cfg-hairline); display: flex; flex-direction: column; gap: 2px; }
.avm-place-below .avm-menu { top: calc(100% + 8px); right: 0; }
.avm-place-side .avm-menu { bottom: 0; left: calc(100% + 8px); }
/* PORTALED (open): avatar-menu.ts lifts the open menu onto <body> so no host
   stacking context (the Bridge header's backdrop-filter shell material, a
   transformed rail) can bury it under a grid — #1125 item 1. It hands the
   anchor's viewport rect and the viewport size over as custom properties;
   placement stays HERE, so the phone flip below is still one media query. */
.avm-menu-portal { position: fixed; top: auto; right: auto; bottom: auto; left: auto; }
.avm-menu-portal[data-place="below"] { top: calc(var(--avm-anchor-bottom) + 8px); right: calc(var(--avm-vw) - var(--avm-anchor-right)); }
.avm-menu-portal[data-place="side"] { bottom: calc(var(--avm-vh) - var(--avm-anchor-bottom)); left: calc(var(--avm-anchor-right) + 8px); }
/* An author display:flex beats the UA's [hidden] rule — without this guard
   the menu ships permanently open. */
.avm-menu[hidden] { display: none; }
.avm-menu.on { animation: avm-pop-in var(--t-standard) var(--ease-out); }
@keyframes avm-pop-in { from { opacity: 0; transform: translateY(-3px) scale(.98); } to { opacity: 1; transform: none; } }
.avm-title, .avm-label { font-size: .62rem; letter-spacing: .14em; text-transform: uppercase; color: var(--cfg-muted); font-weight: 700; padding: 6px 8px 4px; }
.avm-modes { display: flex; gap: 4px; padding: 3px; margin: 0 0 8px; background: var(--cfg-surface); border: 1px solid var(--cfg-line); border-radius: 9px; }
.avm-modebtn { flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 6px 8px; border: 1px solid transparent; border-radius: 7px; background: none; color: var(--cfg-muted); font-size: 12.5px; cursor: pointer; transition: background var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out), border-color var(--t-micro) var(--ease-out), transform var(--t-micro) var(--ease-out); }
.avm-modebtn .icon { width: 15px; height: 15px; }
.avm-modebtn:hover { color: var(--cfg-fg); }
.avm-modebtn:active { transform: translateY(1px); }
.avm-modebtn.on { background: var(--cfg-accent-dim); color: var(--cfg-accent); border-color: color-mix(in srgb, var(--cfg-accent) 42%, transparent); }

/* The dropdowns — layout hooks only; the fc block paints. The pop is a
   POPOVER too, so its surface re-points onto the material tier. */
.avm-dd { position: relative; display: inline-flex; width: 100%; margin: 0 0 8px; --fc-pop-bg: var(--mat-popover-bg, var(--fc-pop-bg)); --fc-pop-shadow: var(--mat-popover-sh, var(--fc-pop-shadow)); }
.avm-dd-btn { gap: 8px; width: 100%; padding: 6px 8px 6px 10px; font-size: 12.5px; transition: border-color var(--t-micro) var(--ease-out), transform var(--t-micro) var(--ease-out); }
.avm-dd-btn:active { transform: translateY(1px); }
.avm-dd-label { flex: 1 1 auto; min-width: 0; display: inline-flex; align-items: center; gap: 8px; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.avm-dd-chev { width: 16px; height: 16px; flex: none; transition: transform var(--t-micro) var(--ease-out); }
.avm-dd-pop { z-index: var(--z-popover-list, 210); width: max-content; max-height: 320px; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.avm-dd-item { gap: 8px; padding: 6px 10px; font-size: 12.5px; white-space: nowrap; }
.avm-dd-item-l { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.avm-tsw { display: inline-flex; gap: 4px; }
.avm-tsw-dot { width: 13px; height: 13px; border-radius: var(--radius-full); border: 1px solid var(--cfg-line); display: inline-block; }
.avm-style-hint { margin-left: auto; flex: none; font: 600 9.5px/1 var(--font-accent); letter-spacing: .1em; text-transform: uppercase; color: var(--cfg-muted); }

.avm-note { margin: 0 0 8px; padding: 0 8px; color: var(--cfg-muted); font-size: 11.5px; line-height: 1.45; }
.avm-sep { height: 1px; margin: 5px 6px; background: var(--cfg-line); }
.avm-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border: none; border-radius: var(--radius-md); background: none; color: var(--cfg-fg); font-size: 13px; text-align: left; text-decoration: none; cursor: pointer; transition: background var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out); }
.avm-item .icon { width: 16px; height: 16px; color: var(--cfg-muted); }
.avm-item:hover { background: var(--cfg-accent-dim); color: var(--cfg-accent); }
.avm-item:hover .icon { color: var(--cfg-accent); }
.avm-who { display: flex; align-items: center; gap: 10px; padding: 6px 8px 4px; min-width: 0; }
.avm-id { min-width: 0; }
.avm-name { font-weight: 600; font-size: 13px; color: var(--cfg-fg); }
.avm-email { color: var(--cfg-muted); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.avm-role { align-self: flex-start; margin: 0 8px 4px; padding: 1px 8px; border-radius: 9px; font-size: 11px; background: var(--cfg-badge-info-bg); color: var(--cfg-badge-info-fg); }

/* Phone: the circle is a thumb target (>= 44px), the rail menu opens UP over
   the bottom tab bar instead of sideways. */
@media (pointer: coarse), (max-width: 640px) {
  /* The thumb target is the primitive's own size seat, not a min-width
     override that would fight `.ibtn`'s pinned box. */
  .avm-place-below .avm-btn { --ibtn-size: 44px; }
  .avm-place-side .avm-menu { bottom: calc(100% + 8px); left: auto; right: 0; }
  .avm-menu-portal[data-place="side"] { bottom: calc(var(--avm-vh) - var(--avm-anchor-top) + 8px); left: auto; right: calc(var(--avm-vw) - var(--avm-anchor-right)); }
}

@media (prefers-reduced-motion: reduce) {
  .avm-btn,
  .avm-dd-btn,
  .avm-dd-chev,
  .avm-item,
  .avm-menu.on,
  .avm-modebtn { animation: none; transition: none; }
}
/* <<< avm-component-block <<< */

/* Square icon-only control (the Bridge's .stripbtn / .railmini). */
/* colour retired to the button intent ramp (Styles × Themes PR-2) */
.hc-iconbtn {
  width: 30px; height: 30px; display: grid; place-items: center; border-radius: var(--radius-md); cursor: pointer;
  transition:
    background var(--t-micro) var(--ease-out),
    color var(--t-micro) var(--ease-out),
    border-color var(--t-micro) var(--ease-out),
    transform var(--t-micro) var(--ease-out);
}
.hc-iconbtn .icon { width: 17px; height: 17px; }
/* .hc-iconbtn:hover: colour retired to the button intent ramp (Styles × Themes PR-2); the class survives as a selector hook only. */
.hc-iconbtn:active { transform: translateY(1px); }

/* ---- row actions: abort / reopen ---- */
/* Icon + word, not icon alone: the glyph carries the destructive/constructive
   distinction at a glance, the word removes the guess. */
.hc-action { display: inline-flex; align-items: center; gap: 5px; }
.hc-action .icon { width: 16px; height: 16px; }
.hc-action-abort { color: var(--hc-danger); }
.hc-action-abort:hover { border-color: var(--hc-danger); background: var(--hc-p-red-bg); }
.hc-action-reopen { color: var(--hc-accent); }
.hc-action-reopen:hover { border-color: var(--hc-accent); background: var(--hc-p-green-bg); }
/* The reopen glyph turns on hover — the affordance IS the action. */
.hc-action-reopen .icon { transition: transform var(--t-standard) var(--ease-out); }
.hc-action-reopen:hover .icon { transform: rotate(180deg); }

/* ---- menus (the release UI's popover, in console tokens) ---- */
.hc-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 200;
  min-width: 232px; padding: 7px;
  /* POPOVER material tier (owner ruling 2026-09-06): the floating overlays —
     the appearance menu, account menu, context menus. Blueprint's popover is
     glass (blur + coating); other styles bring their own. Fallbacks keep the
     panel look if style-materials.css is absent. */
  background: var(--mat-popover-bg, var(--hc-panel));
  border: 1px solid var(--mat-popover-brd, var(--hc-border2));
  backdrop-filter: var(--mat-popover-blur, none);
  -webkit-backdrop-filter: var(--mat-popover-blur, none);
  border-radius: 13px; box-shadow: var(--mat-popover-sh, var(--hc-shadow)), inset 0 1px 0 var(--hc-hairline);
  display: flex; flex-direction: column; gap: 2px;
}
/* An author `display: flex` beats the UA's `[hidden] { display: none }`, so a
   class that sets display AND is toggled via `hidden` needs this guard or the
   menu ships permanently open. The release UI shipped exactly that bug once. */
.hc-menu[hidden] { display: none; }
.hc-menu.on { animation: hc-pop-in var(--t-standard) var(--ease-out); }
.hc-menu-head { display: flex; align-items: center; gap: 10px; padding: 6px 8px 8px; }
.hc-menu-id { min-width: 0; }
.hc-menu-name { font-weight: 600; }
.hc-menu-email {
  color: var(--hc-muted); font-size: 11.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-menu-title, .hc-menu-label {
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--hc-muted); font-weight: 700; padding: 6px 8px 4px;
}
.hc-menu-role {
  align-self: flex-start; margin: 0 8px 4px; padding: 1px 8px; border-radius: 9px;
  font-size: 11px; background: var(--hc-p-violet-bg); color: var(--hc-p-violet-fg);
}
.hc-menu-sep { height: 1px; margin: 5px 6px; background: var(--hc-line); }
.hc-menu-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border: none; border-radius: var(--radius-md); background: none;
  color: var(--hc-fg); font-size: 13px; text-align: left; text-decoration: none;
  cursor: pointer; transition: background var(--t-micro) var(--ease-out), color var(--t-micro) var(--ease-out);
}
.hc-menu-item .icon { width: 16px; height: 16px; color: var(--hc-muted); }
.hc-menu-item:hover { background: var(--hc-accent-dim); color: var(--hc-accent); }
.hc-menu-item:hover .icon { color: var(--hc-accent); }
.hc-menu-danger:hover { background: var(--hc-p-red-bg); color: var(--hc-p-red-fg); }
.hc-menu-danger:hover .icon { color: var(--hc-p-red-fg); }

/* ---- the right-click context menu (items 55.1/55.6/55.7): .hc-menu chrome,
   fixed at the pointer instead of anchored to a wrap ---- */
.hc-ctxmenu { position: fixed; top: 0; right: auto; left: 0; z-index: 320; }
.hc-menu-item:disabled { opacity: .55; cursor: default; }
.hc-menu-item:disabled:hover { background: none; color: var(--hc-fg); }
.hc-menu-item:disabled:hover .icon { color: var(--hc-muted); }
.hc-menu-item-l { flex: 1 1 auto; min-width: 0; }
.hc-menu-item-n { margin-left: auto; color: var(--hc-muted); font-size: 11px; white-space: nowrap; }
.hc-menu-theme { padding: 7px 7px 9px; }

/* ---- themed dropdown — the `.hc-dd*` LAYOUT hooks (PR-3a) --------------
   R69 built these as a twin of the Helm's mkDropdown, paint and all. PR-3a
   retired the twin: dropdown.ts is now an adapter over the ONE primitive
   (pi-stack/src/client/form-controls.ts) and every colour here moved to the
   byte-identical fc-component-block above. What is left is position, size
   and the console-specific overrides — expressed by RE-POINTING the --fc-*
   tokens rather than painting over the component, which is the same
   discipline the intent ramps hold for buttons. primitives-check refuses a
   skin class that carries background / color / border again. */
.hc-dd { position: relative; display: inline-flex; }
.hc-menu-theme .hc-dd { width: 100%; margin: 0 0 8px; }
.hc-dd-btn {
  gap: 8px; width: 100%; padding: 6px 8px 6px 10px; font-size: 12.5px;
  transition:
    border-color var(--t-micro) var(--ease-out),
    transform var(--t-micro) var(--ease-out);
}
.hc-dd-btn:active { transform: translateY(1px); }
.hc-dd-label {
  flex: 1 1 auto; min-width: 0; display: inline-flex; align-items: center; gap: 8px;
  text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-dd-chev {
  width: 16px; height: 16px; flex: none;
  transition: transform var(--t-micro) var(--ease-out);
}
.hc-dd-pop {
  z-index: 210; width: max-content; max-height: 320px;
  padding: 6px; display: flex; flex-direction: column; gap: 2px;
}
.hc-dd-pop.on { animation: hc-pop-in var(--t-standard) var(--ease-out); }
.hc-dd-item { gap: 8px; padding: 6px 10px; font-size: 12.5px; white-space: nowrap; }
.hc-dd-item-l { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ---- segmented dark/light toggle (the release UI's .modes) ---- */
.hc-modes {
  display: flex; gap: 4px; padding: 3px; margin: 0 0 8px;
  background: var(--hc-surface); border: 1px solid var(--hc-line); border-radius: 9px;
}
.hc-modebtn {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 6px 8px; border: 1px solid transparent; border-radius: 7px;
  background: none; color: var(--hc-muted); font-size: 12.5px; cursor: pointer;
  transition:
    background var(--t-micro) var(--ease-out),
    color var(--t-micro) var(--ease-out),
    border-color var(--t-micro) var(--ease-out),
    transform var(--t-micro) var(--ease-out);
}
.hc-modebtn .icon { width: 15px; height: 15px; }
.hc-modebtn:hover { color: var(--hc-fg); }
.hc-modebtn:active { transform: translateY(1px); }
.hc-modebtn.on {
  background: var(--hc-accent-dim); color: var(--hc-accent);
  border-color: color-mix(in srgb, var(--hc-accent) 42%, transparent);
}

/* ---- theme swatch (rides the dropdown rows and closed label) ---- */
.hc-tsw { display: inline-flex; gap: 4px; }
.hc-tsw-dot {
  width: 13px; height: 13px; border-radius: var(--radius-full);
  border: 1px solid var(--hc-line); display: inline-block;
}

/* the style picker's material hint (owner ruling 2026-09-06) — a muted eyebrow
   beside a style's name in the dropdown, the at-a-glance cue the theme swatch
   gives for themes. */
.hc-style-hint {
  margin-left: auto; flex: none;
  font: 600 9.5px/1 var(--font-accent);
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--hc-muted);
}

/* ---- avatar ---- */
.hc-avatar-btn {
  padding: 0; border: none; background: none; cursor: pointer; line-height: 0;
  border-radius: var(--radius-full);
  transition: transform var(--t-micro) var(--ease-out);
}
.hc-avatar-btn:hover { transform: scale(1.06); }
.hc-avatar-btn:active { transform: scale(.97); }
/* THE avatar seat. One size custom property drives width, height AND the
   flex-basis pin together, so a size variant (.hc-avatar-lg) or a context
   (.hc-person rows) can never desynchronize the axes again — the defect was
   exactly that split: a stale duplicate block pinned flex-basis at 28px while
   this block's height said 30/40, and the menu-head avatar painted 28×40. */
.hc-avatar {
  --hc-avatar-size: 30px;
  width: var(--hc-avatar-size); height: var(--hc-avatar-size);
  flex: 0 0 var(--hc-avatar-size); aspect-ratio: 1 / 1;
  border-radius: var(--radius-full);
  display: inline-grid; place-items: center; overflow: hidden;
  background: var(--hc-surface2); color: var(--hc-fg);
  border: 1px solid var(--hc-border2);
  font-size: 11.5px; font-weight: 700; letter-spacing: .02em;
  user-select: none;
}
.hc-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hc-avatar-lg { --hc-avatar-size: 40px; font-size: 14px; }

/* ---- recents cards ---- */
.hc-recents { margin: 0 0 22px; }
.hc-recents[hidden] { display: none; }
.hc-section-head {
  display: flex; align-items: center; gap: 8px; margin: 0 0 10px;
  color: var(--hc-muted);
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase; font-weight: 700;
}
.hc-section-head .icon { width: 14px; height: 14px; }
.hc-cards {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
}
.hc-card {
  display: flex; flex-direction: column; gap: 9px;
  padding: 13px 14px;
  /* CARD material tier (owner ruling 2026-09-06): the dense, repeated surface.
     The blur rides the token exactly as the Helm's `.card` does — the perf
     contract (scripts/perf-budget.mjs) keeps refraction OFF the card tier for
     every style, so `--mat-card-blur` is `none` on all eight and this line is
     an identity today. It is written anyway so the console consumes the same
     six card properties the Helm does: a token nobody reads is a token that
     silently stops mattering. Fallbacks keep the flat surface if
     style-materials.css is absent. */
  background: var(--mat-card-bg, var(--hc-surface));
  border: 1px solid var(--mat-card-brd, var(--hc-line));
  border-radius: var(--hc-radius);
  backdrop-filter: var(--mat-card-blur, none);
  -webkit-backdrop-filter: var(--mat-card-blur, none);
  box-shadow: var(--mat-card-sh, inset 0 1px 0 var(--hc-hairline));
  color: inherit; text-decoration: none;
  animation: hc-card-in var(--t-deliberate) var(--ease-out) both;
  animation-delay: var(--hc-stagger, 0ms);
  transition:
    border-color var(--t-micro) var(--ease-out),
    box-shadow var(--t-standard) var(--ease-out),
    transform var(--t-micro) var(--ease-out);
}
/* CARD HOVER — the last per-style material the home's own chrome did not read
   (owner correction 2026-09-07). The Helm's rule is
     .card:hover { box-shadow: var(--mat-card-glow), var(--mat-card-hi); }
   and `--mat-card-glow` / `--mat-card-hi` are where a style says what a
   hovered surface DOES: Glass's masked specular, Synthwave's neon bloom,
   Quarterdeck's lamp. The console read neither, so a release card hovered with
   Blueprint's shadow under all eight styles.

   BLUEPRINT STAYS AS IT SHIPPED. The console's Blueprint hover is `--hc-shadow`
   plus the hairline, which is NOT what Blueprint's --mat-card-glow/-hi compose
   to, so taking the Helm's rule outright would have re-skinned the identity
   style — a look change smuggled in under a parity fix. The identity
   declaration below is untouched; the per-style arm follows it.

   That console-Blueprint-vs-Helm-Blueprint hover difference is REAL and is
   left standing deliberately: it predates this change, it is style-orthogonal,
   and closing it is a look change the owner has not asked for. Named, not
   fixed. */
a.hc-card:hover {
  border-color: var(--hc-accent);
  box-shadow: var(--hc-shadow), inset 0 1px 0 var(--hc-hairline);
  transform: translateY(-2px);
}
/* …and the per-STYLE arm. Scoped per style id, one selector each, because
   that is the ONLY shape scripts/style-identity.mjs treats as a style overlay:
   the first draft of this used
     :root[data-style]:not([data-style='blueprint']) { --hc-card-hover-sh: … }
   and the identity gate CAUGHT it — the console always stamps
   `data-style="blueprint"`, so an attribute-presence selector is live under
   the identity style and the gate measured the Blueprint hover moving from
   the shipped `0 16px 40px …, inset 0 1px 0 …` to Blueprint's
   `--mat-card-glow, --mat-card-hi` composition in BOTH modes.
   Its arm C proves a `[data-style="<id>"]`-scoped rule cannot move the
   identity rendering, so this is the form that is safe by construction.

   The LIST is not free-floating: style-materials.test.mjs asserts it equals
   the registry's non-identity ids, so a ninth style cannot land with its card
   hover silently on Blueprint's shadow. */
:root[data-style="glass"] a.hc-card:hover,
:root[data-style="synthwave"] a.hc-card:hover,
:root[data-style="quarterdeck"] a.hc-card:hover,
:root[data-style="starship"] a.hc-card:hover,
:root[data-style="studio"] a.hc-card:hover,
:root[data-style="mainframe"] a.hc-card:hover,
:root[data-style="destroyer"] a.hc-card:hover {
  box-shadow: var(--mat-card-glow), var(--mat-card-hi);
}
a.hc-card:active { transform: translateY(1px); }
.hc-card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.hc-card-go { color: var(--hc-muted); display: inline-flex; }
.hc-card-go .icon { width: 15px; height: 15px; }
a.hc-card:hover .hc-card-go { color: var(--hc-accent); }
.hc-card-name {
  font-weight: 600; font-size: 15px; line-height: 1.25;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-card-meta {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  color: var(--hc-muted); font-size: 11.5px;
}
/* .hc-card-owner is GONE (jump-back-in, R44): the owner said fleet status +
   throughput INSTEAD OF the owner name — the card's live cell and its layout
   live in the seam's section anchor below. */
.hc-card-when { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ---- page entrance ---- */
.hc-main { animation: hc-rise-in var(--t-standard) var(--ease-out); }

/* ---- dialogs ----
   The one-shot enter animations that lived here (hc-rise-in on the overlay,
   hc-pop-in on the panel — enter-only, no exit edge) are RETIRED: dialog
   motion is transition-driven on BOTH edges by the dialog engine
   (dialog.ts + the seam:dialog-system anchor below). */

/* THE reduced-motion guard now lives at the END of this file (it must come
   AFTER every seam's section anchor — `animation: none` wins by ORDER at
   equal specificity, so a guard above a seam's rules would guard nothing). */

/* ---- item 55.5: fixed viewport — no Bridge page page-scrolls; all
   scrolling lives inside the grid (the WaaG treatment). The class is the
   SHELL's now (shared-shell, R61): mountShell adds it on every page, so
   #/users and #/audit are viewport-fixed exactly like the release grid. ---- */
body.hc-fixed { height: 100dvh; overflow: hidden; }
body.hc-fixed #app { height: 100%; display: flex; flex-direction: column; min-height: 0; }
body.hc-fixed .hc-main {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
}
body.hc-fixed .hc-recents { flex: none; }
body.hc-fixed .hc-grid { flex: 1 1 auto; min-height: 240px; height: auto; }
body.hc-fixed .hc-listmore-wrap { flex: none; }

/* ---- item 55.4: the sheets-style column filter popover (.hc tokens; the
   GEOMETRY mirrors .gl-flt in helm-bridge styles.css — the two surfaces'
   filter popups are the same instrument in two theme vocabularies) ---- */
.hc-flt { display: flex; flex-direction: column; gap: 7px; width: 232px; padding: 9px; font-size: 12px; color: var(--hc-fg); }
.hc-flt-crit { display: flex; gap: 6px; }
.hc-flt-op { flex: 1 1 55%; min-width: 0; --fc-h: 26px; border-radius: 5px; }
.hc-flt-op .fc-sel-btn { font: inherit; font-size: 11.5px; padding: 0 6px; }
.hc-flt-operand, .hc-flt-search {
  flex: 1 1 45%; min-width: 0;
  background: var(--hc-surface); color: var(--hc-fg);
  border: 1px solid var(--hc-border2); border-radius: 5px; padding: 4px 8px;
  font: inherit; font-size: 12px;
}
.hc-flt-operand:disabled { opacity: .45; }
/* The operator control is NOT in this list, and its absence is measured
   (#1990 follow-up). `.hc-flt-op` is `opts.cls` on mkFcSelect's WRAP — a
   plain <div> with no tabindex (runtime: tabindex attr absent, .tabIndex
   -1), so it can never match :focus-visible. Tabbing into the control puts
   focus on the <button class="fc-sel-btn fc-field"> trigger; the wrap reads
   :focus-within true, :focus-visible FALSE, outline-style none. The trigger
   DOES paint a ring, at outline-offset 2px, and TWO rules would each paint
   exactly that one: `.fc-sel-btn:focus-visible` above, and the universal
   `:focus-visible` near the top of this sheet (--focus-ring-width 2px,
   --focus-ring-offset 2px). WHICH of them paints it is NOT established and
   does not need to be — measured: stripping either alone leaves the ring
   unchanged, stripping both changes it to Chromium's UA default. What
   matters is that it is 2px and the rule removed here was 1px. Re-pointing
   that rule at the trigger would override the shared 2px with 1px and make
   this one control inconsistent for no gain, so the dead selector is removed
   rather than re-aimed. The two that remain are LIVE: both measured matching, at
   outline-offset 1px. Arms: scripts/flt-op-focus-ring.test.mjs. */
.hc-flt-operand:focus-visible, .hc-flt-search:focus-visible {
  outline: 2px solid var(--hc-accent); outline-offset: 1px;
}
.hc-flt-bar { display: flex; align-items: center; gap: 6px; }
.hc-flt-btn {
  background: var(--hc-surface2); color: var(--hc-ink2);
  border: 1px solid var(--hc-border2); border-radius: 5px;
  padding: 3px 8px; font: inherit; font-size: 11px; cursor: pointer;
}
.hc-flt-btn:hover { color: var(--hc-fg); border-color: var(--hc-accent); }
/* the measured "X of Y" — the count the requirement names */
.hc-flt-count { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--hc-muted); font-size: 11px; }
.hc-flt-list { max-height: 240px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.hc-flt-row {
  display: flex; align-items: center; gap: 8px;
  padding: 4px 6px; border-radius: 5px; cursor: pointer; color: var(--hc-ink2);
}
.hc-flt-row:hover { background: var(--hc-track); color: var(--hc-fg); }
.hc-flt-row input { accent-color: var(--hc-accent); cursor: pointer; flex: none; }
.hc-flt-val { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hc-flt-val.blank { color: var(--hc-muted); font-style: italic; }
.hc-flt-n { flex: none; color: var(--hc-muted); font-size: 10.5px; font-variant-numeric: tabular-nums; }
.hc-flt-more { padding: 5px 6px; color: var(--hc-muted); font-size: 11px; font-style: italic; }

/* ---- item 55.9: owner avatars in the grid (the CARD copy died with
   .hc-card-owner — jump-back-in R44) ---- */
.hc-owner { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.hc-owner .hc-avatar { width: 20px; height: 20px; flex: 0 0 20px; font-size: 8.5px; }
.hc-owner-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- item 55.10: branch cells link to the forge when the seat says how ---- */
.hc-branch-link { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--hc-muted); }
.hc-branch-link:hover { color: var(--hc-accent); border-bottom-color: var(--hc-accent); }

/* =====================================================================
   SECTION ANCHORS — one per downstream Bridge-UI seam (collision map §5).
   RULES, not hopes (two clean merges silently reverted tail appends in one
   day in this repo): every seam writes ONLY between its own begin/end
   anchor; tail appends BELOW the last anchor are FORBIDDEN; the reduced-
   motion guard at the very end of the file is the ONLY thing after them
   (it must stay last — `animation: none` wins by order). Each seam also
   registers its animated classes in that guard, one selector per line, at
   the line's alphabetical position.
   ===================================================================== */

/* ===== seam:shared-shell (R60-R68, R72) — write ONLY inside this anchor ===== */

/* ONE shell on all three pages (shell.ts): brand + nav + appearance +
   account in the top strip; the page's own state lives in the .hc-pagebar
   row below it (R64-R68). The pagebar is the below-header host row whose
   right side grid-toolbar's buttons later land beside — that seam restyles
   #hcColsBtn/#hcCreate; this one only re-homed them unchanged. */

/* The brand is an ANCHOR now (it navigates home from every page) — without
   this it would paint browser link-blue over the accent-glowed helm. */
a.hc-brand { color: var(--hc-fg); text-decoration: none; }

.hc-nav { display: flex; align-items: center; gap: 4px; margin-left: 8px; }
/* Acknowledgment, not movement: colour/background only, so this needs no
   reduced-motion registration (the guard disarms transform/opacity). */
.hc-navlink {
  transition:
    background var(--t-micro) var(--ease-out),
    color var(--t-micro) var(--ease-out);
}

/* The page-state row (R64). This is the surface that CHANGES on navigation
   — it carries the entrance (R58) so the identical top strip above it can
   stay put and read as one persistent application (R60/R72). */
.hc-pagebar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  padding: 14px 20px 12px;
  /* part of the shell chrome band (theme-truth): the deep ground step +
     the same hairline the Helm's strips carry (.trailbar/.subtabstrip) */
  background: var(--hc-bg-deep);
  border-bottom: 1px solid var(--hc-line);
  animation: hc-rise-in var(--t-standard) var(--ease-out);
}
.hc-pagetitle {
  margin: 0; font-size: 15px; font-weight: 600; letter-spacing: .02em;
}

/* An author display beats the UA's [hidden] { display: none } (the .rc-menu
   incident, and the hidden-attr e2e sweep): .hc-driftpill and .hc-btn both
   declare display: inline-flex, so the pagebar's hidden states — the drift
   pill when the fleet is current, #hcCreate for a user who may not create —
   need this guard. Scoped to the row so it also covers what grid-toolbar
   lands here later. */
.hc-pagebar [hidden] { display: none; }

/* R61: body.hc-fixed is the SHELL's on every page now, so the admin pages'
   rows above the grid must hold their height while the grid flexes. */
body.hc-fixed .hc-pagebar { flex: none; }
body.hc-fixed .hc-toolbar { flex: none; }

/* ---- Phone bottom tab bar + "⋯" header sheet (owner ruling 2026-09-06) ----
   The unified thumb-reachable navigation. On a phone the four header nav
   destinations move OUT of the .hc-header row into a fixed bottom tab bar, and
   help/appearance/account collapse behind the .hc-morebtn "⋯" trigger into
   the .hc-headtools sheet. This is what BUYS BACK the shell's min-content
   width: the non-wrapping header nav is the ~547px floor the mobile guard
   measured (e2e/specs/mobile-responsive.readonly.spec.mjs) and every
   console-origin surface shares one header, so emptying it to brand + one
   trigger (~140px) is what finally lets the Bridge home, config-templates and
   api-tokens lay out at true device width. Desktop (>640px) never matches the
   media block, so the top strip is byte-identical there. No bare hex — every
   colour rides an --hc-* token (this file's rule). The bottom bar is a FLOW
   child at the base of the hc-fixed column, so it reserves its own height and
   nothing is occluded. */
.hc-headtools { display: inline-flex; align-items: center; gap: 12px; }
.hc-morebtn { display: none; }
.hc-tabbar { display: none; }
.hc-tab {
  flex: 1 1 0; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  min-height: 52px; padding: 6px 4px;
  color: var(--hc-muted); text-decoration: none;
  font-size: 11px; font-weight: 600; letter-spacing: .01em;
  transition: color var(--t-micro) var(--ease-out), background var(--t-micro) var(--ease-out);
}
.hc-tab .icon { width: 22px; height: 22px; }
.hc-tab[hidden] { display: none; }
.hc-tab:hover { color: var(--hc-fg); }
.hc-tab-on { color: var(--hc-accent); }
/* THE PHONE QUERY: portrait (<= 640px wide) OR a landscape phone (<= 960px
   wide AND <= 500px tall — an iPhone 14 rotated is 844 x 390, a Pro Max 932 x
   430; no desktop window is that short). Width/height only, never
   (pointer: coarse): a landscape phone at 844px is wider than the portrait
   breakpoint, so without the height leg it rendered the DESKTOP header nav +
   28px links and no reachable bottom bar (measured by the @mobile x styles
   matrix, 2026-09-07). Three files carry this query in lockstep — this block,
   portal.css (.kbp-*) and api.css (.apid-*); change all three or none. */
@media (max-width: 640px), ((max-width: 960px) and (max-height: 500px)) {
  .hc-header { position: relative; }
  .hc-nav { display: none; }
  .hc-morebtn { display: inline-flex; } /* the primitive's own display — grid would drop its centring */
  .hc-headtools {
    position: absolute; top: calc(100% + 6px); right: 8px; z-index: 60;
    display: none; flex-direction: column; align-items: stretch; gap: 6px;
    min-width: 200px; padding: 8px;
    background: var(--hc-panel); color: var(--hc-fg);
    border: 1px solid var(--hc-line); border-radius: var(--radius-xl);
    box-shadow: var(--hc-shadow);
  }
  .hc-headtools.hc-headtools-open { display: flex; }
  .hc-tabbar {
    display: flex; flex: none; align-items: stretch;
    border-top: 1px solid var(--hc-line);
    background: var(--mat-shell-bg, var(--hc-bg-deep));
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}

/* ===== end seam:shared-shell ===== */

/* ===== seam:dialog-system (R1-R5, R62) — write ONLY inside this anchor ===== */

/* THE dialog idiom (dialog.ts drives the classes; motion.ts owns timing):
   enter = [hidden] off + `open` on a double-rAF; exit = `leaving` +
   transitionend with the 400ms fallback. Overlay fades, panel pops; both
   EDGES animate (the console had zero exit animations before this seam).
   Every colour rides an --hc-* token — R3 lands because the foundation
   seam (PR 368) widened the theme's reach to scrim/danger/shadow. */
.hc-modal {
  opacity: 0;
  transition: opacity var(--t-standard) var(--ease-out);
}
.hc-modal.open { opacity: 1; }
.hc-modal.leaving {
  opacity: 0;
  pointer-events: none; /* the POST rides behind the close — nothing here is clickable */
  transition: opacity var(--t-standard) var(--ease-in);
}
.hc-modal .hc-dialog {
  opacity: 0;
  transform: translateY(10px) scale(.97);
  transition:
    opacity var(--t-standard) var(--ease-out),
    transform var(--t-standard) var(--ease-out);
  /* MODAL elevation rides the chosen style's modal shadow (owner ruling
     2026-09-06); Blueprint's floors to the shipped --hc-shadow. */
  box-shadow: var(--mat-modal-sh, var(--hc-shadow));
  min-width: 340px;
}
.hc-modal.open .hc-dialog { opacity: 1; transform: none; }
.hc-modal.leaving .hc-dialog {
  opacity: 0;
  transform: translateY(6px) scale(.98);
  transition-timing-function: var(--ease-in);
}
/* Phone: release the 340px modal floor so the .hc-dialog viewport clamp
   (max-width: min(480px, 100vw - 32px)) can govern below ~372px, where the
   floor would otherwise win and re-introduce edge overflow (audit Lane A). */
@media (max-width: 560px) {
  .hc-modal .hc-dialog { min-width: 0; }
}

/* R1/R5 — the confirmation LAYOUT: title line, consequence body, actions
   right-aligned with the verb-labelled PRIMARY rightmost; destructive verbs
   carry the danger emphasis ON THE PRIMARY (never on Cancel). */
.hc-dialog-title,
.hc-dialog h2,
.hc-dialog h3 { margin: 0 0 6px; font-size: 15px; font-weight: 650; color: var(--hc-fg); }
.hc-dialog-body { margin: 0 0 4px; color: var(--hc-ink2); font-size: 13px; line-height: 1.5; white-space: pre-line; }
.hc-dialog .hc-dialog-actions { justify-content: flex-end; margin-top: 16px; }
.hc-btn-primary-danger {
  background: var(--hc-danger); border-color: var(--hc-danger); color: var(--hc-bg);
}
.hc-btn-primary-danger:hover {
  background: color-mix(in srgb, var(--hc-danger) 82%, var(--hc-fg));
  border-color: color-mix(in srgb, var(--hc-danger) 82%, var(--hc-fg));
}

/* R4 — interactions INSIDE the dialog (buttons already ride .hc-btn's
   token transitions). Busy: the confirming button spins for the beat it
   stays visible while the dialog leaves and the POST rides behind. */
.hc-btn-busy { pointer-events: none; }
.hc-btn-busy::after {
  content: ''; display: inline-block; width: 10px; height: 10px;
  margin-left: 7px; vertical-align: -1px; border-radius: var(--radius-full);
  border: 2px solid currentColor; border-top-color: transparent;
  animation: hc-dlg-spin 700ms linear infinite;
}
@keyframes hc-dlg-spin { to { transform: rotate(360deg); } }

/* R4 — an error/refusal message ARRIVES (showDialogMsg re-fires this per
   message, so a second refusal is visibly new). */
.hc-msg-in { animation: hc-msg-in var(--t-standard) var(--ease-out); }
@keyframes hc-msg-in {
  from { opacity: 0; transform: translateY(-3px); }
}

/* R4 — the focus ring settles onto each control as the trap moves focus. */
.hc-dialog :focus-visible {
  outline: 2px solid var(--hc-accent);
  outline-offset: 2px;
  animation: hc-focus-pop var(--t-micro) var(--ease-out);
}
@keyframes hc-focus-pop {
  from { outline-offset: 5px; }
  to { outline-offset: 2px; }
}
/* ===== end seam:dialog-system ===== */

/* ===== seam:framework-column (R11-R19) — write ONLY inside this anchor ===== */
/* The Framework column's state machine: ONE pill, five painted states
   (Current / Update Available / Updating… / Update failed / version
   unknown). The pill IS the affordance (R15): drift and failed render as a
   real <button class="hc-pill … hc-fw-go">; there is no separate Update
   button any more (R14/R15). */
.hc-fw .hc-pill { display: inline-flex; align-items: center; gap: 6px; }
/* R11 — current shown POSITIVELY (owner ruling 2026-09-04). */
.hc-pill-current { background: var(--hc-p-green-bg); color: var(--hc-p-green-fg); }
/* R12 — the running install breathes while it works… */
.hc-pill-updating {
  background: var(--hc-p-blue-bg); color: var(--hc-p-blue-fg);
  animation: hc-pill-pulse 1.6s var(--ease-out) infinite;
}
/* The quiescent lane's wait: scheduled, not yet installing — steady (no
   pulse: nothing is running on the VM yet, and a breathing pill would claim
   otherwise). */
.hc-pill-scheduled { background: var(--hc-p-blue-bg); color: var(--hc-p-blue-fg); }
/* R16 — …and a failure ENTERS visibly rather than just being there. */
.hc-pill-failed {
  background: var(--hc-p-red-bg); color: var(--hc-p-red-fg);
  animation: hc-fw-fail-in var(--t-standard) var(--ease-out);
}
/* A pill that is a button: reset the chrome, keep the pill look,
   acknowledge hover/press (press motion registered in the guard).
   THE GEOMETRY PIN (bridge-ui-polish item A): `font: inherit` alone made
   the button chip inherit the grid cell's larger font — this selector
   (0-1-1) beats .hc-pill (0-1-0), so the pill's 12px/18px typography lost
   and Update Available painted taller and wider-padded than the Current /
   Updating… spans. The family still inherits; the size and line-height are
   re-pinned to .hc-pill's own figures so a button chip and a span chip
   measure the same box (release-chip-geometry.check.mjs proves it in a
   real DOM). */
button.hc-fw-go {
  border: 0; cursor: pointer; font: inherit; padding: 1px 10px;
  font-size: 12px; line-height: 18px;
  transition: transform var(--t-micro) var(--ease-out), filter var(--t-micro);
}
button.hc-fw-go:hover { filter: brightness(1.12); }
button.hc-fw-go:active { transform: scale(.96); }
button.hc-fw-go:focus-visible { outline: 2px solid var(--hc-accent); outline-offset: 1px; }
/* R12's loading indicator — a ring that spins. Under reduced motion the
   animation is disarmed (guard) and the ring stands still; the label
   beside it still reads 'Updating…', so the state is never motion-only. */
.hc-fw-spin {
  width: 10px; height: 10px; border-radius: var(--radius-full); flex: none;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  animation: hc-fw-spin .9s linear infinite;
}
/* R13 — the settle back to Current when an install lands (replaces the
   'update complete' popup, R14). */
.hc-fw-just-updated { animation: hc-fw-settle var(--t-deliberate) var(--ease-spring); }
@keyframes hc-fw-spin { to { transform: rotate(1turn); } }
@keyframes hc-fw-settle {
  0% { transform: scale(.86); opacity: .35; }
  55% { transform: scale(1.06); }
  100% { transform: scale(1); opacity: 1; }
}
@keyframes hc-fw-fail-in {
  0% { transform: translateY(-3px); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
/* ===== end seam:framework-column ===== */

/* ===== seam:active-users-column (R37-R40) — write ONLY inside this anchor ===== */

/* The Active-users cell: up to AVATAR_STACK_MAX (home.ts) overlapping
   avatars — avatarNode, the ONE renderer — then a '+N more' chip. Names
   live in each wrapper's data-tip (the app-wide tooltip layer) and in the
   column FILTER's multi-value list, never only in the pictures. Every
   colour rides a token (the no-bare-hex rule). */
.hc-au { display: inline-flex; align-items: center; }
.hc-au-a {
  display: inline-flex; position: relative; border-radius: var(--radius-full);
  /* the ring that makes the overlap read as a STACK, not a smear */
  box-shadow: 0 0 0 2px var(--hc-bg);
  /* entrance: each avatar rises in on its own beat (--au-i from the
     renderer); `backwards` holds the from-state through the delay */
  animation: hc-au-in var(--t-standard) var(--ease-out) backwards;
  animation-delay: calc(var(--au-i, 0) * 40ms);
  transition: transform var(--t-micro);
}
.hc-au-a:not(:first-child) { margin-left: -7px; }
.hc-au-a:hover { transform: translateY(-2px); z-index: 1; }
.hc-au-a .hc-avatar { width: 20px; height: 20px; flex: 0 0 20px; font-size: 8.5px; }
/* connected-now (lane 'socket') wears the accent ring; a recent visitor
   stays neutral — the tooltip names the same distinction in words */
.hc-au-live { box-shadow: 0 0 0 2px var(--hc-bg), 0 0 0 3.5px var(--hc-accent); }
.hc-au-more {
  margin-left: 8px; padding: 2px 7px; border-radius: var(--radius-pill);
  font-size: 10.5px; white-space: nowrap; cursor: default;
  color: var(--hc-muted); background: var(--hc-surface2);
  border: 1px solid var(--hc-border2);
  transition: transform var(--t-micro), color var(--t-micro);
}
.hc-au-more:hover { transform: translateY(-1px); color: var(--hc-fg); }
@keyframes hc-au-in { from { opacity: 0; transform: translateX(-5px); } }

/* R58 'filter interactions' (this seam's clause): the shared value-list
   popover's rows and buttons acknowledge hover/press with a FADE. Color
   transitions only — acknowledgment, not movement, per the guard's own
   doctrine — so no reduced-motion guard line is owed (the walker tracks
   transform/opacity). Lives in THIS anchor because this seam's R58 clause
   names the filter surface and no other seam of this program owns
   .hc-flt-*; a later owner may supersede in place. */
.hc-flt-btn,
.hc-flt-row {
  transition: background-color var(--t-micro), color var(--t-micro),
    border-color var(--t-micro);
}

/* ===== end seam:active-users-column ===== */

/* ===== seam:jump-back-in (R41-R49) — write ONLY inside this anchor ===== */

/* -- card top row: identity left (pill + release number), tools right
      (pin badge + open arrow). The top keeps its space-between layout;
      these two wrappers are what stop four children from spreading. -- */
.hc-card-idents { display: inline-flex; align-items: center; gap: 7px; min-width: 0; }
.hc-card-tools { display: inline-flex; align-items: center; gap: 6px; }
.hc-card-relid {
  color: var(--hc-muted); font-size: 11px; font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: .02em; white-space: nowrap;
}

/* -- R41: the pinned badge + the pinned card's border accent -- */
.hc-card-pin { color: var(--hc-accent); display: inline-flex; }
.hc-card-pin .icon { width: 13px; height: 13px; }
.hc-card-pinned { border-color: var(--hc-accent-dim); }

/* -- R44: the live cell (fleet counts + throughput waveform) where the
      owner name used to sit. .hc-count/.hc-ic/.hc-wave are the grid cells'
      own classes — same vocabulary, same tokens. -- */
.hc-card-live {
  display: flex; align-items: center; gap: 10px;
  color: var(--hc-muted); font-size: 11.5px; min-height: 20px;
}
.hc-card-live .hc-wave { margin-left: auto; }
.hc-card-nolive { opacity: .7; }

/* -- R45: total cost, tabular so a repaint never wobbles the row -- */
.hc-card-cost { font-variant-numeric: tabular-nums; }
/* #927 — the Total cost column. A figure reads as a figure (tabular digits
   line up down the column); a NON-figure reads as secondary text, so an
   operator scanning for money sees the numbers and not the words, while the
   words are still there to be read and hovered. Deliberately not a pill:
   these are not states to act on, they are the reason a number is absent. */
.hc-cost { font-variant-numeric: tabular-nums; }
.hc-cost-pending,
.hc-cost-replay,
.hc-cost-unknown { color: var(--hc-muted); font-variant-numeric: normal; }
/* #1816 — a LOWER-BOUND total is never painted like a complete one. The
   label already carries the '+' suffix, so the grid's text sort, the recents
   cards and the export all keep it wherever the label travels; this is the
   visual half, borrowing .hc-branch-link's dotted rule (the one "there is
   more on the tip" affordance this sheet already has). It rides ALONGSIDE
   the state class, never instead of it. */
.hc-cost-truncated { border-bottom: 1px dotted var(--hc-muted); }
/* #4360 — a billed cell shows the vendor figure in place of the catalog one;
   its coverage mark is secondary text, shown only when the bill does not
   cover every token. */
.hc-cost-coverage { margin-left: 0.5em; color: var(--hc-muted); font-variant-numeric: normal; }

/* -- re-render discipline: the strip is repainted in place when the cost
      map / fleet sweep land, every fleet tick, and on pin/unpin. The
      recreated elements must NOT replay the one-shot entrance — only a card
      genuinely NEW to the strip (hc-card-new, set by the FLIP helper) still
      enters. Specificity note: the -new rule must out-rank the kill (4
      classes vs 3), and it does. -- */
.hc-recents.hc-no-entrance .hc-card { animation: none; }
.hc-recents.hc-no-entrance .hc-card.hc-card-new {
  animation: hc-card-in var(--t-deliberate) var(--ease-out) both;
}

/* -- R47: the pin/unpin beat on the toggled card (hc-pin-just is added for
      one animation and removed on animationend) -- */
@keyframes hc-pin-pulse {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.03); }
  100% { transform: none; }
}
.hc-recents .hc-card.hc-pin-just { animation: hc-pin-pulse var(--t-standard) var(--ease-out); }
.hc-pin-just .hc-card-pin { animation: hc-pop-in var(--t-standard) var(--ease-out); }

/* -- R48: survivors travel to their new slot. withFlip (motion.ts) adds
      hc-card-flip for exactly the playback frame(s); the deliberate duration
      is scoped here so hover/press keep their micro timing. -- */
.hc-recents .hc-card.hc-card-flip { transition: transform var(--t-deliberate) var(--ease-out); }

/* -- card field freshness: fires ONCE per card when '—' becomes a measured
      value (cost map / fleet sweep landing), never on routine repaints -- */
@keyframes hc-cell-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.hc-cell-fresh { animation: hc-cell-in var(--t-standard) var(--ease-out); }

/* ===== end seam:jump-back-in ===== */

/* ===== seam:mobile-cards — write ONLY inside this anchor =====
   The phone "card / list per row" fallback (owner ruling 2026-09-06): on a
   phone the Bridge's wide data grids render as stacked key:value cards, one
   per row (card-list.ts), instead of a horizontal-scroll table, and the
   fixed-viewport cockpit relaxes to document scroll so the card column flows.
   The console serves only list/admin/form pages (releases, users, audit,
   configuration templates + its editor), so this relaxation never reaches a
   live cockpit surface — that is the Helm's separate client. Desktop is
   byte-identical: the card column is hidden and the grid frame governs above
   the breakpoint. Colours ride the same CARD material tier the recents cards
   consume (--mat-card-*), so a style switch moves these too. */

/* The card column — hidden on desktop, where the grid IS the list. */
.hc-cardlist { display: none; }
.hc-cardlist-empty { color: var(--hc-muted); padding: 26px 4px; text-align: center; font-size: 13px; }

/* A data card: the recents CARD material transposed into a title/badge row
   over a key:value definition list. Reuses .hc-card (material, border, radius,
   entrance) with the row-transpose layout layered on. */
.hc-datacard { gap: 11px; }
.hc-datacard .hc-card-top { align-items: flex-start; gap: 10px; }
.hc-datacard .hc-card-name { white-space: normal; }
.hc-datacard-badge { flex: none; }
.hc-cardkv {
  margin: 0; display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: 6px 14px; align-items: baseline;
}
.hc-cardkv-k { color: var(--hc-muted); font-size: 11.5px; letter-spacing: .02em; }
.hc-cardkv-vwrap { margin: 0; min-width: 0; text-align: right; }
.hc-cardkv-v {
  font-size: 12.5px; color: var(--hc-fg);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  /* Relax the fixed-viewport cockpit for the console's list/admin/form pages:
     the document scrolls and the card column flows, rather than trapping the
     page in 100dvh with the grid as the only scroller. html joins body in the
     relaxation so no ancestor keeps a 100dvh/overflow-hidden clamp that would
     strand the release card list below the fold (owner feedback 2026-09-06 —
     "can only see the first few release cards, can't scroll down"). */
  html { height: auto; overflow: visible; }
  body.hc-fixed { height: auto; min-height: 100dvh; overflow: visible; }
  body.hc-fixed #app { height: auto; display: block; min-height: 0; }
  body.hc-fixed .hc-main { flex: none; display: block; min-height: 0; }

  /* iOS scroll hardening: the console body paints the style-background layer
     with background-attachment: fixed (--bgl-attach), which iOS Safari
     composites into a fixed layer that fights a tall, document-scrolling page
     and can leave the list feeling stuck. The Helm sidesteps this with a
     separate fixed #style-bg element; the Bridge home paints on the body
     itself, so on a phone the background scrolls with the content instead. */
  body { background-attachment: scroll; }

  /* The grid frame gives way to the card column. */
  .hc-grid { display: none; }
  .hc-cardlist { display: flex; flex-direction: column; gap: 12px; }

  /* Reconcile the co-landed bottom tab bar (the mobile-nav PR) with document
     scroll: it ships as a FLOW child at the base of the hc-fixed column, which
     relies on the 100dvh cockpit to pin it; once the page scrolls as a
     document it would scroll away, so pin it to the viewport and reserve its
     height on the scrolling content so it never occludes the last card.
     Overridden from this seam (source order wins) rather than editing that
     PR's shell block, so the rebase stays clean. */
  body.hc-fixed .hc-tabbar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 60; }
  body.hc-fixed .hc-main { padding-bottom: calc(64px + env(safe-area-inset-bottom, 0px)); }
}

/* LANDSCAPE phone (the phone query's second leg alone — portrait is carded
   above and never reaches the grid): the AG grid keeps rendering, inside the
   100dvh cockpit column with the bottom tab bar as a flow child. The desktop
   `min-height: 240px` grid floor does not fit a 390px-tall column (header 69
   + pagebar 50 + toolbar 42 + bar 55 leave ~150px): measured 2026-09-07, the
   grid ran to y=425 in a 390px viewport and painted OVER the tab bar. Relax
   the floor so the column closes; the grid still shows its header + rows. */
@media (max-width: 960px) and (max-height: 500px) {
  body.hc-fixed .hc-grid { min-height: 96px; }
}

/* Touch tap targets (LIVE @mobile guard, landscape-touch viewport): the shell
   header's mode toggle (#hcModeBtn, the .ibtn primitive at 34px) and the account
   avatar button (#hcAccountBtn, a padding-0 wrapper around the 30px .hc-avatar)
   each grow their OWN rect to the >=44px touch floor on a coarse pointer. The
   guard measures getBoundingClientRect(), so the earlier absolutely-positioned
   ::before overlay enlarged the effective hit area but left the measured rect
   small and STILL flagged — enlarge the element itself instead. #hcModeBtn bumps
   the primitive's --ibtn-size (its moon/sun glyph keeps its own --ibtn-glyph
   size, centered by .ibtn's flex); #hcAccountBtn becomes a 44px centered flex
   well so the avatar keeps its size and place. The audit Lane A rule below
   already sizes the header's .hc-iconbtn / nav links / brand. Scoped to the
   console shell header so the portals' own header copies and the dense
   grid/dialog buttons are untouched. Layout only, no colour.
   The query is pointer-modality OR the phone query (portrait / landscape
   phone, see the shared-shell seam): a phone that reports a fine pointer —
   or an instrument that lost its touch emulation — still gets the floor. */
@media (pointer: coarse), (max-width: 640px), ((max-width: 960px) and (max-height: 500px)) {
  .hc-header #hcModeBtn { --ibtn-size: 44px; }
  .hc-header #hcAccountBtn {
    min-width: 44px; min-height: 44px;
    display: inline-flex; align-items: center; justify-content: center;
  }
}
/* ===== end seam:mobile-cards ===== */

/* ===== seam:appearance-dialog (R69-R71) — write ONLY inside this anchor ===== */
/* ===== end seam:appearance-dialog ===== */

/* ===== seam:grid-toolbar (R26-R33) — write ONLY inside this anchor ===== */

/* The grid-side control bar (R26/R30): rides .hc-toolbar (the admin pages'
   structurally identical row — body.hc-fixed already pins its height via
   shared-shell's flex:none) with the buttons pushed flush right, exactly
   where WaaG's .gl-spring parks #glCols/#glReset. */
.hc-gridbar { padding: 0 0 8px; }
/* #hcCreate arrives [hidden] for a user who may not create — this row needs
   the same author-display guard the pagebar carries (the .rc-menu incident;
   .hc-gtbtn's inline-flex would beat the UA's [hidden] without it). */
.hc-gridbar [hidden] { display: none; }

/* Icon button, spec'd to WaaG's .gl-ibtn BY MEASURE (28px box, 14px glyph,
   radius 6, surface fill, border) — a deliberate decision (seam contract)
   over re-speccing .hc-iconbtn, whose 30px/17px/radius-8 transparent shape
   every existing shell control wears. Token vocabulary maps 1:1:
   --surface-2→--hc-surface2, --ink-2→--hc-ink2, --border-2→--hc-border2. */
/* colour retired to the button intent ramp (Styles × Themes PR-2) */
.hc-gtbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; flex: none; border-radius: var(--radius-sm); cursor: pointer;
  transition: color var(--t-micro) var(--ease-out),
              border-color var(--t-micro) var(--ease-out),
              background var(--t-micro) var(--ease-out),
              transform var(--t-micro) var(--ease-out);
}
.hc-gtbtn .icon { width: 17px; height: 17px; }
/* .hc-gtbtn:hover: colour retired to the button intent ramp (Styles × Themes PR-2); the class survives as a selector hook only. */
/* The house press — same 1px drop as .hc-btn/.hc-iconbtn (R58). */
.hc-gtbtn:active { transform: translateY(1px); }
.hc-gtbtn-primary {
  background: var(--hc-accent); color: var(--hc-onaccent); border-color: var(--hc-accent);
}
.hc-gtbtn-primary:hover { color: var(--hc-onaccent); filter: brightness(1.08); }

/* Popover open beat (R58) — the same pop the dropdowns use, on the column
   picker this bar owns. Closing hides via [hidden]; entrance-only, like
   every other popover on this screen. */
.hc-colsmenu:not([hidden]) { animation: hc-pop-in var(--t-micro) var(--ease-out); }

/* Refresh spin — runs only while the fetch is in flight (the class rides
   the Promise.all window in home.ts), so a stuck spinner means a stuck
   request, never a forgotten class. */
.hc-gtbtn.hc-spinning .icon { animation: hc-spin 800ms linear infinite; }
@keyframes hc-spin { to { transform: rotate(360deg); } }

/* ===== end seam:grid-toolbar ===== */

/* ===== seam:abort-flow (R6-R10) — write ONLY inside this anchor ===== */
/* ===== end seam:abort-flow ===== */

/* ===== seam:create-release-modal (R50, R53-R55) — write ONLY inside this anchor ===== */
/* ===== end seam:create-release-modal ===== */

/* ===== seam:branch-tree-and-sha-timeline (R51-R52a, R56-R57) — write ONLY inside this anchor ===== */

/* R51/R52 — the branch-browse dialog: a hand-rolled '/'-segment tree with
   the seven ADO columns. Status/PR cells render their NAMED unavailability
   ('not available'); the server's full reason sentence rides each cell's
   AND header's title VERBATIM. Grid tracks: star | the 7 columns | overflow. */
/* Item 10 (D9): WIDE, and max-width:none because .hc-dialog's 480px
   max-width clamp silently capped the old width:960px — the browser modal
   has been rendering at 480px since it shipped. */
.hc-bb-dialog { width: min(1180px, calc(100vw - 48px)); max-width: none; }
.hc-bb-status { margin: 0 0 8px; }
.hc-bb-filter {
  width: 100%; box-sizing: border-box; margin: 0 0 8px; padding: 6px 8px;
  background: var(--hc-surface); color: var(--hc-fg);
  border: 1px solid var(--hc-line); border-radius: var(--radius-sm); font-size: 13px;
}
.hc-bb-head,
.hc-bb-row {
  display: grid;
  grid-template-columns: 26px minmax(150px, 1.5fr) 92px minmax(80px, 1fr) 150px 90px 96px 108px 26px;
  gap: 8px; align-items: center;
}
.hc-bb-head { padding: 4px 6px; border-bottom: 1px solid var(--hc-line); }
.hc-bb-h {
  font-size: 11px; font-weight: 650; color: var(--hc-muted);
  text-transform: uppercase; letter-spacing: .04em; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* Item 11/12: the tree is its own explorer SURFACE (inset token, bordered)
   and its height is FIXED from open — folders expanding never resize the
   modal; unused space stays blank. */
.hc-bb-tree {
  height: 46vh; overflow: auto;
  background: var(--hc-surface); border: 1px solid var(--hc-line);
  border-radius: var(--radius-sm); padding: 4px;
}
.hc-bb-row {
  padding: 3px 6px; border-radius: var(--radius-sm); cursor: pointer;
  transition: background-color var(--t-micro) var(--ease-out), transform var(--t-micro) var(--ease-out);
}
.hc-bb-row:hover { background: var(--hc-surface2); transform: translateX(2px); }
.hc-bb-sel { background: var(--hc-surface2); box-shadow: inset 0 0 0 1px var(--hc-accent); }
.hc-bb-row-in { animation: hc-bb-row-in var(--t-standard) var(--ease-out); }
@keyframes hc-bb-row-in { from { opacity: 0; transform: translateY(-3px); } }
/* .hc-bb-c is the row's shared CELL class — it dresses text cells AND the star
   button. The star is an `.ibtn` now, so the cell ink is scoped away from it;
   everything else in the row is unchanged. */
.hc-bb-c:not(.ibtn) {
  font-size: 12.5px; color: var(--hc-ink2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hc-bb-c.ibtn { font-size: 12.5px; }
.hc-bb-c-branch, .hc-bb-name { color: var(--hc-fg); }
.hc-bb-c-commit { font-family: var(--font-data); }
.hc-bb-unavail { color: var(--hc-muted); font-style: italic; }
.hc-bb-folder .hc-bb-name { font-weight: 600; }
.hc-bb-count { margin-left: 6px; color: var(--hc-muted); font-size: 11px; }
/* Layout only. The rotation, its token and its reduced-motion arm come from
   the shared disc-component-block rule, off the aria-expanded this row
   already carried — this wrapper's own transform transition is retired with
   the `.hc-bb-closed` rule it partnered. */
.hc-bb-chev { display: inline-flex; margin-right: 4px; vertical-align: -2px; }
.hc-bb-chev .icon { width: 16px; height: 16px; }
/* .hc-bb-more is still a raw overflow TRIGGER (PR-3b owns it) and keeps its
   own paint; .hc-bb-star is an `.ibtn` and takes colour from the ramp. */
.hc-bb-more {
  background: none; border: 0; padding: 2px; cursor: pointer;
  color: var(--hc-muted); border-radius: var(--radius-xs); line-height: 0;
}
.hc-bb-star { padding: 2px; border-radius: var(--radius-xs); line-height: 0; }
.hc-bb-more:hover { color: var(--hc-fg); background: var(--hc-surface2); }
.hc-bb-star .icon, .hc-bb-more .icon { width: 15px; height: 15px; }
/* THE STATE-COLOUR PATTERN: starred RE-POINTS the neutral ramp rather than
   overriding it, so hover/disabled/focus stay derived (see .scope-gcanon.on
   in the Helm's sheet for the same rule stated once). The filled glyph is the
   redundant, non-colour channel. */
.hc-bb-star.hc-bb-on {
  --btn-neutral-ink: var(--hc-accent);
  --btn-neutral-hover-ink: var(--hc-accent);
  --btn-neutral-border: color-mix(in srgb, var(--hc-accent) 45%, transparent);
}
.hc-bb-star.hc-bb-on .ibtn-glyph { fill: currentColor; }
.hc-bb-star-just { animation: hc-bb-star-pop var(--t-standard) var(--ease-out); }
@keyframes hc-bb-star-pop { 30% { transform: scale(1.35); } }
.hc-bb-none { margin: 8px 6px; color: var(--hc-muted); font-size: 12.5px; }

/* R56/R57 — the SHA timeline: openForkPicker's row idiom (.hc-roleopt) plus
   the per-row corpus chip (link/bootstrap/pending); the measured walk
   sentences live in the chip TOOLTIPS (the footer line was removed by
   owner item 19, batch 2026-09-05). */
.hc-st-dialog { width: min(840px, calc(100vw - 48px)); max-width: none; }
.hc-st-status { margin: 0 0 8px; }
.hc-st-list { max-height: 46vh; overflow: auto; display: flex; flex-direction: column; gap: 4px; }
/* Item 16: the cards sit on their OWN surface against the panel modal. */
.hc-st-row {
  transition: background-color var(--t-micro) var(--ease-out);
  background: var(--hc-surface); border: 1px solid var(--hc-line);
  border-radius: var(--radius-sm); display: flex; gap: 10px; align-items: flex-start;
}
.hc-st-main { flex: 1; min-width: 0; }
/* Item 17: author + datetime in one consistent right-hand column. */
.hc-st-meta {
  flex: none; width: 172px; display: flex; flex-direction: column;
  align-items: flex-end; gap: 2px; padding: 2px 4px 0 0;
  font-size: 11px; color: var(--hc-muted);
}
.hc-st-author { color: var(--hc-ink2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.hc-st-date { font-family: var(--font-data); white-space: nowrap; }
.hc-st-row-in { animation: hc-st-row-in var(--t-standard) var(--ease-out); }
@keyframes hc-st-row-in { from { opacity: 0; transform: translateY(-3px); } }
.hc-st-sha { font-family: var(--font-data); }
.hc-st-b {
  display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: var(--radius-pill);
  font-size: 10px; font-weight: 700; letter-spacing: .03em; vertical-align: 1px;
}
.hc-st-b-tip { background: var(--hc-p-violet-bg); color: var(--hc-p-violet-fg); }
.hc-st-chip-link { background: var(--hc-p-green-bg); color: var(--hc-p-green-fg); }
.hc-st-chip-boot { background: var(--hc-p-amber-bg); color: var(--hc-p-amber-fg); }
.hc-st-chip-pending { background: var(--hc-p-neutral-bg); color: var(--hc-p-neutral-fg); }
.hc-st-chip-in { animation: hc-st-chip-in var(--t-standard) var(--ease-out); }
@keyframes hc-st-chip-in { from { opacity: 0; transform: scale(.85); } }

/* Phone: the fixed 172px meta column starves the content on a ~360px viewport
   (audit Lane A, item 8) — stack the meta as a full-width footer line (author
   left, date right) under the main content. */
@media (max-width: 560px) {
  .hc-st-row { flex-direction: column; gap: 4px; }
  .hc-st-meta {
    width: 100%; flex-direction: row; align-items: center;
    justify-content: space-between; padding: 0;
  }
  .hc-st-author { min-width: 0; }
}

/* D7 (item 9): the stacked overlay host — the browse modals open ABOVE
   the create form, which stays mounted. Same .hc-modal machinery (its
   transitions are already in the reduced-motion guard). Z LADDER TERMS
   (onboarding-css.test.mjs pins the console ladder at <= 1000 so the ob
   scrim's 1200 tops everything): the stacked host keeps the base modal's
   OWN z (1000) and wins by DOM ORDER — stackHostFor appends it AFTER the
   base host, and a later sibling paints above among equal z-indexes. */
.hc-modal-stacked { z-index: 1000; }

/* The create-modal wiring (seam 14's named mount seats, decorated here):
   compact Browse buttons + the R57 corpus line — its arrival entrance is
   dialog-system's registered .hc-msg-in (showDialogMsg re-fires it), so
   this block adds NO new animated selectors. */
.hc-create-browse { padding: 4px 10px; font-size: 12px; white-space: nowrap; }
/* The R57 corpus-answer line sits on its OWN line below the SHA field row
   (seam:create-modal-fields); the field-row flex layout lives up in the
   create-form block. Keeps a fixed one-line reservation so the modal never
   changes size (D4). */
.hc-create-corpus {
  margin: 4px 0 0; font-size: 12px; color: var(--hc-ink2); font-family: var(--font-code);
  overflow-wrap: anywhere; height: 32px; overflow-y: auto;
}
/* ===== end seam:branch-tree-and-sha-timeline ===== */

/* ===== seam:users-audit-parity (R59) — write ONLY inside this anchor ===== */

/* R59b: the admin toolbars now carry .hc-gtbtn's inline-flex buttons — the
   same author-display hazard .hc-gridbar guards against (the .rc-menu
   incident: any author `display` beats the UA's [hidden]). Guard the whole
   .hc-toolbar family so a [hidden] toggle inside any of the three toolbar
   rows keeps meaning what it says. Every other surface this seam ships —
   the column picker pop, the ctx menu, the filter popover, the refresh
   spin, the button press — is a class an earlier seam already registered
   in the reduced-motion guard; this seam adds NO new animated selector
   (its R58 line: k = 0, justified in the seam PR). */
.hc-toolbar [hidden] { display: none; }

/* ===== end seam:users-audit-parity ===== */

/* ------------------------------------------------------------------------
   Configuration templates (owner item 65, PR 2) — the LIST screen's own
   rules. The EDITOR moved to the shared cfg-component-block above (Helm
   settings re-implementation, owner ruling 2026-09-08): one editor, two
   homes, byte-identical. Tokens only — every colour rides an --hc-* custom
   property; transitions only, no new keyframes. */

.hc-ct-showdis { display: inline-flex; align-items: center; gap: 6px; color: var(--hc-muted); font-size: 13px; white-space: nowrap; }
.hc-ct-name { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.hc-ct-name-text { overflow: hidden; text-overflow: ellipsis; }
.hc-ct-badge-default { background: var(--hc-p-blue-bg); color: var(--hc-p-blue-fg); }
.hc-ct-badge-disabled { background: var(--hc-p-neutral-bg); color: var(--hc-p-neutral-fg); }
.hc-ct-ovr { color: var(--hc-fg); }
.hc-ct-ovr-none { color: var(--hc-muted); }
.hc-ct-back { color: var(--hc-muted); text-decoration: none; white-space: nowrap; margin-right: 10px; }
.hc-ct-back:hover { color: var(--hc-fg); }

/* History dialog (PR 3) */
.hc-ct-histwhen { float: right; color: var(--hc-muted); font-size: 11px; }
.hc-ct-histline { display: block; color: var(--hc-fg); font-size: 12.5px; }

/* ===== seam:create-modal-fields (owner batch 2026-09-05) — write ONLY inside this anchor ===== */

/* D4 — the modal's box NEVER changes size: fixed dialog width, and every
   variable-height seat carries a fixed reservation (the chips row, the
   hint, the corpus line and the message seat are edited in place in the
   create-form section above; the template row below reserves its space and
   toggles VISIBILITY, never [hidden]/display). The probe half of
   home.test.mjs greps these reservations and drives the real dialog
   through every verdict state asserting nothing in the form flow flips
   [hidden] after build. */
.hc-create-dialog { width: min(520px, calc(100vw - 48px)); max-width: none; }
.hc-field-wrap { position: relative; display: block; margin-top: 4px; flex: 1; min-width: 0; }
/* Full-width input; padding-right reserves the in-field .hc-vicon seat so
   text never slides under the check/X. */
.hc-field-wrap input { margin-top: 0; width: 100%; padding-right: 30px; }
.hc-create-branchrow .hc-field-wrap,
.hc-create-sharow .hc-field-wrap { margin-top: 0; }

/* D3 — the in-field verdict icon (check/X). The base transform is static
   POSITIONING (vertical centring), not interaction movement. */
.hc-vicon {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; display: inline-flex; pointer-events: auto;
}
.hc-vicon .icon { width: 16px; height: 16px; }
.hc-vicon.hc-vicon-ok { color: var(--hc-p-green-fg); }
.hc-vicon.hc-vicon-bad { color: var(--hc-danger); }
.hc-vicon.hc-vicon-in { animation: hc-vicon-pop var(--t-standard) var(--ease-spring); }
.hc-vicon.hc-vicon-out { animation: hc-vicon-fade var(--t-micro) var(--ease-in) forwards; }
@keyframes hc-vicon-pop {
  from { opacity: 0; transform: translateY(-50%) scale(.4); }
  to   { opacity: 1; transform: translateY(-50%) scale(1); }
}
@keyframes hc-vicon-fade {
  to { opacity: 0; transform: translateY(-50%) scale(.6); }
}
/* Screen-reader parity for the removed verdict text (one per field). */
.hc-visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* D6 — the typeahead pop: an absolute OVERLAY under the branch input
   (opening it moves no layout). Entrance matches .hc-dd-pop's beat. */
.hc-ta-pop {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 40;
  background: var(--hc-panel); border: 1px solid var(--hc-border2);
  border-radius: var(--radius-sm); box-shadow: var(--hc-shadow); max-height: 240px;
  overflow-y: auto; padding: 4px;
  animation: hc-pop-in var(--t-micro) var(--ease-out);
}
.hc-ta-item {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
  background: none; border: 0; color: var(--hc-fg); border-radius: var(--radius-xs);
  padding: 5px 8px; font-size: 12.5px; cursor: pointer;
}
.hc-ta-item .icon { width: 14px; height: 14px; color: var(--hc-muted); flex: none; }
.hc-ta-item:hover, .hc-ta-item.hc-ta-active { background: var(--hc-surface2); }
.hc-ta-item.hc-ta-active { outline: 1px solid var(--hc-accent); }
.hc-ta-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hc-ta-count { color: var(--hc-muted); font-size: 11px; margin-left: auto; }
.hc-ta-more { color: var(--hc-muted); font-size: 11px; padding: 4px 8px; }

/* D5 — the reserved template row (visibility machine) + full-width
   custom dropdown. */
.hc-create-tplrow { height: 58px; visibility: hidden; }
.hc-create-tplrow.hc-tpl-ready { visibility: visible; }
.hc-create-tplrow .hc-tpl-dd { margin-top: 4px; }
.hc-create-tplrow .hc-dd, .hc-create-tplrow .hc-dd-btn { width: 100%; }

/* Item 5/15 — icon-only action buttons: square hit target, glyph centred,
   the button NAME on hover via data-tip (doubles as aria-label). */
.hc-btn-icon { display: inline-flex; align-items: center; justify-content: center; padding: 6px; line-height: 0; }
.hc-btn-icon .icon { width: 16px; height: 16px; }

/* ===== end seam:create-modal-fields ===== */

/* =====================================================================
   THE reduced-motion guard (R58) — LAST in the file BY CONTRACT.
   SCOPED to this screen's own animated classes rather than a blanket
   `* { animation: none }`: the release UI takes the same approach, and a
   global kill would also silence AG Grid's own affordances, which are not
   ours to decide about. Transitions on hover/press stay — they are
   acknowledgment, not movement, and removing them makes controls feel
   broken — but the MOVEMENT they would carry is disarmed (transform: none).

   STRUCTURE (owned by design-system-foundation; downstream seams own only
   their lines): three arms — animation-kill, transform-kill,
   transition-kill — each ONE SELECTOR PER LINE in plain byte-wise
   ALPHABETICAL order. Insert your line at its alphabetical position;
   same-tail-offset appends are the clean-merge silent loss this repo
   measured twice in one day. motion-coverage.test.mjs walks every animated
   selector in this file and REFUSES any that is not registered here — and
   refuses an out-of-order arm — so a guard line lost to a clean merge is a
   CI failure, not a silent regression. */
@media (prefers-reduced-motion: reduce) {
  .as-badge,
  .as-dot-busy,
  .as-launcher-live,
  .as-msg-assistant,
  .as-msg-user,
  .as-working-bubble,
  .hc-au-a,
  .hc-bb-row-in,
  .hc-bb-star-just,
  .hc-btn-busy::after,
  .hc-cand,
  .hc-card,
  .hc-cell-fresh,
  .hc-ckpt-skel,
  .hc-colsmenu:not([hidden]),
  .hc-create-form input.hc-input-invalid,
  .hc-dd-pop.on,
  .hc-dialog :focus-visible,
  .hc-fw-just-updated,
  .hc-fw-spin,
  .hc-gtbtn.hc-spinning .icon,
  .hc-main,
  .hc-menu.on,
  .hc-msg-in,
  .hc-pagebar,
  .hc-pill-busy,
  .hc-pill-failed,
  .hc-pill-updating,
  .hc-pin-just .hc-card-pin,
  .hc-recents .hc-card.hc-pin-just,
  .hc-recents.hc-no-entrance .hc-card.hc-card-new,
  .hc-st-chip-in,
  .hc-st-row-in,
  .hc-ta-pop,
  .hc-ts-live .hc-ts-dot,
  .hc-vicon.hc-vicon-in,
  .hc-vicon.hc-vicon-out,
  .hc-vm-running .hc-ts-dot,
  .pi-step.running .pi-step-label,
  .pi-steps.live .pi-steps-sum {
    animation: none;
  }
  .as-launcher:hover,
  .hc-action-reopen:hover .icon,
  .hc-au-a:hover,
  .hc-au-more:hover,
  .hc-audit-filter button,
  .hc-avatar-btn:active,
  .hc-avatar-btn:hover,
  .hc-bb-row:hover,
  .hc-btn:active,
  .hc-card,
  .hc-dd-btn:active,
  .hc-gtbtn:active,
  .hc-iconbtn:active,
  .hc-invite button,
  .hc-modebtn:active,
  a.hc-card:hover,
  button.hc-fw-go:active {
    transform: none;
  }
  .as-launcher,
  .as-launcher-hidden,
  .as-panel,
  .bridge-tip,
  .hc-bb-chev,
  .hc-create-form input,
  .hc-dd-chev,
  .hc-modal,
  .hc-modal .hc-dialog,
  .hc-modal.leaving,
  .hc-recents .hc-card.hc-card-flip,
  .pi-step-chev,
  .pi-step-detail-in,
  .pi-steps-chev,
  .pi-steps-in,
  .toast {
    transition: none;
  }
}

/* ------------------------------------------------------------------ */
/* toast (Styles x Themes PR-3b) — the shared primitive's seat.         */
/* MEASURED DEFECT this closes: public/index.html has carried a #toast  */
/* element since the shared pi-stack client landed, and this stylesheet */
/* had NO `.toast` rule at all — so every toast an RM chat raised here  */
/* (pi-view's send failures, bookmarks' "Delete refused") rendered as   */
/* unstyled text in the corner of the page. Same four INTENTS as the    */
/* Helm's, in this surface's vocabulary.                                */
/* ------------------------------------------------------------------ */
.toast {
  /* z 1000 is the console ladder's cap below the onboarding scrim (1201) —
     onboarding-css.test.mjs measures it. The toast is a direct <body> child
     appended after the app root, so at equal z it still paints above a modal
     by DOM order. */
  position: fixed; bottom: 26px; left: 26px; z-index: 1000;
  background: var(--hc-panel); color: var(--hc-fg);
  border: 1px solid var(--hc-line); border-left: 3px solid var(--hc-line);
  font: 600 13px var(--font-ui); padding: .6em 1.1em; border-radius: 7px;
  opacity: 0; transform: translateY(8px);
  transition: opacity var(--t-standard) var(--ease-out), transform var(--t-standard) var(--ease-out);
  pointer-events: none; max-width: min(420px, calc(100vw - 52px));
  box-shadow: var(--hc-shadow);
}
.toast[data-toast="neutral"] { border-color: var(--btn-neutral-border); border-left-color: var(--hc-ink2); }
.toast[data-toast="additive"] { border-color: var(--btn-additive-border); border-left-color: var(--btn-additive-ink); }
.toast[data-toast="destructive"] { border-color: var(--btn-destructive-border); border-left-color: var(--btn-destructive-ink); }
.toast[data-toast="error"] { border-color: var(--btn-error-border); border-left-color: var(--btn-error-ink); }
.toast.show { opacity: 1; transform: none; }

/* ------------------------------------------------------------------ */
/* API tokens page (#/tokens, item 61) — one fenced hunk               */
/* ------------------------------------------------------------------ */
.hc-pat-token {
  font-family: var(--font-code); /* R35 — every face through the token */
  font-size: 12px;
  opacity: 0.85;
}
.hc-pat-scopes { display: inline-flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.hc-scopelist { max-height: 300px; overflow-y: auto; }

/* ------------------------------------------------------------------ */
/* assistant overlay (work item 64 PR 1) — token seat.                 */
/* One pi-stack implementation (pi-stack/client/assistant-overlay.js)  */
/* mounts here AND on the Helm release UI; the structural block below  */
/* this seat is byte-mirrored with helm-bridge/src/client/styles.css   */
/* and pinned by smoke's testAssistantOverlay mirror check. Only this  */
/* seat names host tokens; the structural block speaks --as-*          */
/* exclusively. Animated selectors are registered in the single        */
/* reduced-motion guard above, per this file's structure rule.         */
/* ------------------------------------------------------------------ */
.as-root {
  /* Cross-host palette ruling (owner; the PR 459 follow-up flagged in PR 490):
     the assistant overlay is MESSAGING, not reading — it rides the UI
     palette and --font-ui on EVERY host. This seat used to prefer the
     --pi-* CHAT slots appearance.ts writes on <html>, while the Helm's
     twin seat fell through to its UI tokens (the Helm seats --pi-* per
     chat root, never on <html>) — the same widget in two palettes, under
     Blueprint too. No --pi-* here, ever; the Bridge smoke pins the absence
     on both hosts. Muted ink, lines and raised fills stay color-mix
     DERIVATIONS over the seat's own fg/bg. Send rides the app-level
     --send accent. */
  --as-bg: var(--hc-panel);
  --as-surface: color-mix(in srgb, var(--as-fg) 6%, var(--as-bg));
  --as-fg: var(--hc-fg);
  --as-fg-2: color-mix(in srgb, var(--as-fg) 55%, var(--as-bg));
  --as-line: color-mix(in srgb, var(--as-fg) 14%, var(--as-bg));
  --as-accent: var(--hc-accent);
  --as-onaccent: var(--hc-onaccent);
  --as-danger: var(--hc-danger);
  --as-send: var(--send);
  --as-onsend: var(--send-ink);
  --as-cursor: var(--hc-accent);
  --as-shadow: var(--hc-shadow);
  font-family: var(--font-ui);
}
/* >>> transcript-steps-component-block >>> ONE component, two surfaces: the
   Helm's agent chat transcript and the assistant overlay's, which renders
   through the SAME module (pi-stack transcript-steps-dom.ts). This block is
   mirrored BYTE-IDENTICALLY into helm-console/src/client/styles.css — the
   gate (primitives-nav-check arm 7's idiom) refuses any drift by byte count.
   It speaks --pi-*; the overlay's .as-root seat feeds those slots from its own
   --as-* tokens, so one block paints the chat palette on the chat surface and
   the UI palette in the widget. EDIT BOTH COPIES OR NEITHER. */
/* ---- the step timeline (Claude-desktop "Used …, ran 3 commands · 2 notes")
   Group + per-step details animate via the grid-template-rows 0fr→1fr trick:
   height animates without JS measurement and the rest of the thread moves
   with it. Collapsed content STAYS in the DOM (overflow hidden at 0fr) —
   detail bodies are the exception, filled lazily on first expand. */
.pi-steps-head { display: flex; align-items: center; gap: 6px; width: 100%; text-align: left; background: none; border: 0; padding: 2px 0; color: inherit; cursor: pointer; font: inherit; }
.pi-steps-sum { font-size: .95em; color: color-mix(in srgb, var(--pi-fg) 62%, var(--pi-bg)); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pi-steps-head:hover .pi-steps-sum { color: var(--pi-fg); }
.pi-steps-chev { display: inline-flex; width: 13px; height: 13px; opacity: .4; flex: none; transition: transform var(--t-standard) var(--ease-out); }
.pi-steps-chev svg { width: 13px; height: 13px; }
.pi-steps.open .pi-steps-chev { transform: rotate(180deg); }
.pi-steps-body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--t-deliberate) var(--ease-out); }
.pi-steps.open > .pi-steps-body { grid-template-rows: 1fr; }
.pi-steps-in { overflow: hidden; min-height: 0; opacity: 0; transition: opacity var(--t-deliberate) var(--ease-out); }
.pi-steps.open .pi-steps-in { opacity: 1; }
/* Live shimmer on the summary while the run is open. */
.pi-steps.live .pi-steps-sum {
  background: linear-gradient(90deg, color-mix(in srgb, var(--pi-fg) 55%, var(--pi-bg)) 25%, var(--pi-fg) 50%, color-mix(in srgb, var(--pi-fg) 55%, var(--pi-bg)) 75%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: pi-shimmer 1.8s linear infinite;
}
@keyframes pi-shimmer { from { background-position: 200% 0; } to { background-position: 0% 0; } }

.pi-tl { display: flex; flex-direction: column; padding: 8px 0 2px; }
.pi-step { display: flex; gap: 10px; }
.pi-step-rail { display: flex; flex-direction: column; align-items: center; width: 24px; flex: none; }
.pi-step-ico { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; flex: none; color: color-mix(in srgb, var(--pi-fg) 55%, var(--pi-bg)); background: color-mix(in srgb, var(--pi-fg) 6%, var(--pi-bg)); border-radius: 7px; }
.pi-step-ico svg { width: 14px; height: 14px; }
.pi-step-ico.letter { font-size: .82em; font-weight: var(--pi-weight-bold, 700); font-family: var(--pi-font-ui, var(--font-ui)); }
.pi-step-rail::after { content: ''; flex: 1; width: 1px; min-height: 8px; margin: 4px 0 0; background: color-mix(in srgb, var(--pi-fg) 13%, var(--pi-bg)); }
.pi-step:last-child .pi-step-rail::after { display: none; }
.pi-step-main { flex: 1; min-width: 0; padding-bottom: 16px; }
.pi-step:last-child .pi-step-main { padding-bottom: 2px; }
/* Label row: head left, the step's timestamp right. */
.pi-step-row { display: flex; align-items: baseline; gap: 10px; }
.pi-step-row > .pi-step-head { flex: 1 1 auto; min-width: 0; }
.pi-step-time { flex: none; font-size: .8em; color: color-mix(in srgb, var(--pi-fg) 40%, var(--pi-bg)); font-variant-numeric: tabular-nums; white-space: nowrap; }
.pi-step-head { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; background: none; border: 0; padding: 0; color: color-mix(in srgb, var(--pi-fg) 62%, var(--pi-bg)); cursor: pointer; font: inherit; font-size: .98em; line-height: 24px; }
.pi-step-head:hover { color: var(--pi-fg); }
.pi-step-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pi-step-chev { display: inline-flex; width: 12px; height: 12px; flex: none; opacity: 0; transition: opacity var(--t-micro) var(--ease-out), transform var(--t-standard) var(--ease-out); }
.pi-step-chev svg { width: 12px; height: 12px; }
.pi-step-head:hover .pi-step-chev, .pi-step.expanded .pi-step-chev { opacity: .5; }
.pi-step.expanded .pi-step-chev { transform: rotate(180deg); }
.pi-step.running .pi-step-label {
  background: linear-gradient(90deg, color-mix(in srgb, var(--pi-fg) 55%, var(--pi-bg)) 25%, var(--pi-fg) 50%, color-mix(in srgb, var(--pi-fg) 55%, var(--pi-bg)) 75%);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: pi-shimmer 1.8s linear infinite;
}
.pi-step.iserr .pi-step-label, .pi-step.iserr .pi-step-ico { color: var(--pi-err); }
/* Per-step detail (image-6 layout): the same grid-rows animation. */
.pi-step-detail { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--t-deliberate) var(--ease-out); }
.pi-step-detail.open { grid-template-rows: 1fr; }
.pi-step-detail-in { overflow: hidden; min-height: 0; opacity: 0; transition: opacity var(--t-deliberate) var(--ease-out); }
.pi-step-detail.open .pi-step-detail-in { opacity: 1; }
.pi-dbox { border: 1px solid color-mix(in srgb, var(--pi-fg) 12%, var(--pi-bg)); background: color-mix(in srgb, var(--pi-fg) 4%, var(--pi-bg)); border-radius: var(--radius-lg); padding: 10px 12px; margin-top: 8px; }
.pi-dbox-t { font-size: .85em; opacity: .6; margin-bottom: 6px; }
.pi-dbox pre { margin: 0; font-family: var(--pi-font, var(--font-code)); font-size: .88em; white-space: pre-wrap; word-break: break-word; max-height: 320px; overflow-y: auto; }
.pi-dbox.err { border-color: color-mix(in srgb, var(--pi-err) 30%, var(--pi-bg)); background: color-mix(in srgb, var(--pi-err) 9%, var(--pi-bg)); }
.pi-dbox.err pre, .pi-dbox.err .pi-dbox-t { color: var(--pi-err); }
.pi-think-md { font-size: .95em; color: color-mix(in srgb, var(--pi-fg) 72%, var(--pi-bg)); max-height: 400px; overflow-y: auto; margin-top: 6px; }
/* <<< transcript-steps-component-block <<< */

/* ---- assistant overlay structural (mirrored) ----
 * BYTE-MIRRORED between helm-bridge/src/client/styles.css and
 * helm-console/src/client/styles.css, pinned by smoke's
 * testAssistantOverlay mirror check: one implementation
 * (pi-stack/client/assistant-overlay.js) mounts on both surfaces, so the
 * geometry must not drift. Colors resolve ONLY through the --as-* seat
 * each host defines just above this block — never a host token, never a
 * hex, in here. Reduced-motion handling is per-host and lives OUTSIDE
 * this block (the console registers in its single tail guard; the Bridge
 * carries a section-local block). */
.as-launcher {
  position: fixed;
  right: 18px;
  bottom: 18px;
  width: 46px;
  height: 46px;
  border-radius: var(--radius-full);
  z-index: 139;
  display: grid;
  place-items: center;
  background: var(--as-accent);
  color: var(--as-onaccent);
  border: 1px solid var(--as-line);
  box-shadow: var(--as-shadow);
  cursor: pointer;
  transition: transform var(--t-standard) var(--ease-spring), opacity var(--t-micro) var(--ease-out);
}
.as-launcher svg { width: 28px; height: 28px; }
.as-launcher:hover { transform: scale(1.06); }
.as-launcher:focus-visible { outline: 2px solid var(--as-accent); outline-offset: 2px; }
.as-launcher-hidden {
  /* the genie's other half: the avatar lingers 100ms while the panel grows
   * out of it; the return leg (class removed) snaps back with no delay */
  transform: scale(0);
  opacity: 0;
  pointer-events: none;
  transition-delay: 100ms;
}
.as-launcher-live { animation: as-pulse 2s var(--ease-in) infinite; }
.as-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--as-danger);
  color: var(--as-bg);
  font: 600 0.66rem/18px var(--font-accent);
  text-align: center;
  animation: as-pop var(--t-standard) var(--ease-spring) both;
}
.as-panel {
  /* FLOATING OVERLAY, not a full-height pane (owner ruling 2026-09-05):
   * bounded height, comfortable width, anchored to the launcher corner,
   * rounded + elevated. position:fixed — the app under it never reflows. */
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 140;
  display: flex;
  flex-direction: column;
  width: min(420px, calc(100vw - 36px));
  height: min(640px, calc(100dvh - 96px));
  background: var(--as-bg);
  color: var(--as-fg);
  border: 1px solid var(--as-line);
  border-radius: 16px;
  overflow: hidden;
  box-shadow: var(--as-shadow);
  /* grow out of / collapse into the launcher's center (shared corner,
   * launcher center 23px in from it) */
  transform-origin: calc(100% - 23px) calc(100% - 23px);
  transition:
    transform var(--t-deliberate) var(--ease-spring),
    opacity var(--t-deliberate) var(--ease-out),
    border-radius var(--t-deliberate) var(--ease-out),
    width var(--t-deliberate) var(--ease-out),
    height var(--t-deliberate) var(--ease-out);
}
.as-panel[data-state="closed"] {
  transform: scale(0.04);
  opacity: 0;
  border-radius: 3rem;
  pointer-events: none;
}
.as-panel.as-expanded {
  width: min(760px, calc(100vw - 36px));
  height: min(820px, calc(100dvh - 96px));
}
/* pocket viewports: the floating widget takes the width, never the chrome */
@media (max-width: 640px) {
  .as-panel,
  .as-panel.as-expanded {
    right: 8px;
    left: 8px;
    bottom: 8px;
    width: auto;
    height: min(78dvh, calc(100dvh - 76px));
  }
}
.as-head {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 8px 8px 0;
  border-bottom: 1px solid var(--as-line);
}
.as-tabs {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-wrap: nowrap;
  align-items: flex-end;
  gap: 2px;
  /* ONE AXIS. The strip scrolls sideways and ONLY sideways (owner report
     2026-09-08: a horizontal rail under the tabs AND a vertical rail beside
     the expand/close buttons). `overflow-x: auto` alone computes overflow-y
     to auto as well, and the tabs used to carry `margin-bottom: -1px` — a
     34px tab in a 33px flex line is a 1px vertical scroll range, and the
     registered rail (rail-registry.ts, narrow) paints whatever axis
     scrolls. Hidden here, the y axis grows no rail and reserves no gutter;
     the strip's height is its one row of tabs (never fixed: the rail's
     block-end gutter rides inside a border-box height). */
  overflow-x: auto;
  overflow-y: hidden;
}
.as-tab {
  height: 34px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: 8px 8px 0 0;
  background: none;
  color: var(--as-fg-2);
  font-size: 0.72rem;
  white-space: nowrap;
  cursor: pointer;
  max-width: 200px;
}
.as-tab.on {
  background: var(--as-bg);
  border-color: var(--as-line);
  border-top: 2px solid var(--as-accent);
  color: var(--as-fg);
}
.as-tab:focus-visible { outline: 2px solid var(--as-accent); outline-offset: -2px; }
.as-tab-label {
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 150px;
}
.as-tab-confirm { font-size: 0.62rem; color: var(--as-danger); }
.as-tab-x { opacity: 0.55; padding: 2px 3px; }
.as-tab-x:hover { opacity: 1; }
.as-dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-full);
  border: 1px dashed var(--as-fg-2);
}
.as-dot-busy {
  border-style: solid;
  border-color: var(--as-accent);
  border-top-color: transparent;
  animation: as-spin 1s linear infinite;
}
.as-dot-unread { border: none; background: var(--as-danger); }
/* Styles × Themes PR-3c: the new-tab affordance is an `.ibtn` on the neutral
   intent ramp now, so its COLOUR retired to the ramp (buttons-check refuses a
   cls: class that still paints). What is left is layout: it sits on the tab
   strip's baseline, not the tabs' rounded-top shelf. */
.as-newtab { align-self: center; }
.as-headbtns { display: flex; gap: 4px; padding-bottom: 6px; }
/* .as-iconbtn retired (Styles × Themes PR-2): the panel-header controls
   are `.ibtn.ibtn-xs` now, coloured by the neutral intent ramp. */
.as-transcript {
  flex: 1;
  overflow-y: auto;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* The step timeline's TOKEN BRIDGE. The shared component block above is the
 * Helm chat's own, so it speaks --pi-*; the overlay must not inherit the chat
 * palette (the .as-root ruling — the smoke pins the absence). Feeding those
 * slots from the widget's own --as-* seat is what lets ONE block paint both
 * surfaces: same rules, same class names, each surface's own colours. Scoped
 * to the transcript, the only place a step row renders. */
.as-transcript {
  --pi-fg: var(--as-fg);
  --pi-bg: var(--as-bg);
  --pi-err: var(--as-danger);
  --pi-font-ui: var(--font-ui);
  --pi-font: var(--font-code);
}
/* Bottom-anchored chat idiom: content shorter than the pane sits against
 * the composer on BOTH mount paths (fresh chat and re-hydrated history);
 * taller content scrolls normally. The empty placeholder keeps its own
 * margin:auto centering, so it is excluded here. */
.as-transcript > :first-child:not(.as-empty) { margin-top: auto; }
/* the widget's own thin scrollbar — tokenized, both hosts */
/* The overlay transcript's five hand-rolled scrollbar rules retired in
   PR-3c: it is a registered scrollable now (rail-registry.ts, standard
   variant), so the rail hides the native bar and paints the treatment. */
.as-msg {
  max-width: 75%;
  padding: 10px 14px;
  border-radius: var(--radius-2xl);
  font-family: var(--font-ui);
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* Own bubble = the Helm chat's QUIET fill (fg-8% over the chat ground),
 * never a loud accent slab; the row wrapper right-aligns it and carries
 * the sent-time line underneath (the .pi-msg.user idiom). */
.as-msg-row-user {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.as-msg-user {
  background: color-mix(in srgb, var(--as-fg) 8%, var(--as-bg));
  animation: as-send-in var(--t-standard) var(--ease-spring) both;
}
.as-msg-time {
  flex-basis: 100%;
  text-align: right;
  font-size: 0.78em;
  opacity: 0.55;
  margin-top: 2px;
}
/* Assistant turn = the Helm's INCOMING bubble, mirrored left (owner
 * ruling 2026-09-05: replies get bubbles like the user's — never bare
 * full-width prose). The row left-aligns the bubble on the SURFACE
 * ground wearing the typing bubble's hairline border (the fill alone
 * sits 2% from the user's quiet fill — the border is what reads
 * distinct); the .pi-md prose rides INSIDE; the usage line sits UNDER
 * the bubble as the user row's .as-msg-time twin, mirrored left. Chat
 * text rides --font-ui (the re-role this block used to defer); code
 * rides --font-code. */
.as-msg-row-assistant {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
}
.as-msg-assistant {
  background: var(--as-surface);
  border: 1px solid color-mix(in srgb, var(--as-fg) 12%, var(--as-bg));
  white-space: normal;
  animation: as-receive-in var(--t-standard) var(--ease-out) both;
}
.as-msg-usage {
  flex-basis: 100%;
  text-align: left;
  font-size: 0.78em;
  opacity: 0.55;
  margin-top: 2px;
}
/* The .pi-md prose inside the bubble is styled HERE, self-contained:
 * the hosts' own markdown rules diverge (the Bridge's .md speaks host
 * tokens; the console home defines none at all), so parity means this
 * block owns the whole treatment — explicit margins throughout because
 * only one host zeroes UA defaults.
 * The FAMILY is declared here, not inherited: the assistant overlay is
 * MESSAGING and rides --font-ui on EVERY host (owner's chat-surface ruling,
 * the .as-root seat above). Its bubble body carries both `md` and `pi-md`,
 * so leaving the family to inheritance would let the Bridge's prose reading
 * role — or the pi chat's --pi-* appearance seat — reach a widget the
 * ruling says neither may touch, and it would diverge by host again (the
 * cross-host divergence PR 459 found). Stated once, on both hosts,
 * byte-identical. */
.as-msg-assistant .pi-md { font-family: var(--font-ui); font-size: 1em; line-height: 1.5; color: var(--as-fg); overflow-wrap: anywhere; }
.as-msg-assistant .pi-md > :first-child { margin-top: 0; }
.as-msg-assistant .pi-md > :last-child { margin-bottom: 0; }
.as-msg-assistant .pi-md p { margin: 0 0 0.6em; }
.as-msg-assistant .pi-md :is(h1, h2, h3, h4, h5, h6) { font-family: var(--font-ui); font-size: 1.05em; line-height: 1.35; font-weight: 700; margin: 0.9em 0 0.35em; }
.as-msg-assistant .pi-md :is(strong, b) { font-weight: 700; }
.as-msg-assistant .pi-md :is(ul, ol) { margin: 0 0 0.6em; padding-left: 1.25em; }
.as-msg-assistant .pi-md li { margin: 0.15em 0; }
.as-msg-assistant .pi-md :is(code, pre) { font-family: var(--font-code); }
.as-msg-assistant .pi-md code { font-size: 0.92em; padding: 0 4px; border-radius: var(--radius-xs); background: color-mix(in srgb, var(--as-fg) 7%, var(--as-bg)); border: 1px solid color-mix(in srgb, var(--as-fg) 10%, var(--as-bg)); }
.as-msg-assistant .pi-md pre { margin: 0 0 0.6em; padding: 8px 10px; border-radius: var(--radius-md); font-size: 0.92em; line-height: 1.45; background: color-mix(in srgb, var(--as-fg) 5%, var(--as-bg)); overflow-x: auto; }
.as-msg-assistant .pi-md pre code { font-size: 1em; padding: 0; border: none; background: none; }
.as-msg-assistant .pi-md a { color: var(--as-accent); background: none; text-decoration: underline; text-underline-offset: 2px; text-decoration-color: color-mix(in srgb, var(--as-accent) 50%, transparent); }
.as-msg-assistant .pi-md blockquote { margin: 0 0 0.6em; padding: 2px 0 2px 10px; border-left: 3px solid var(--as-line); color: var(--as-fg-2); }
.as-msg-assistant .pi-md hr { border: 0; border-top: 1px dashed var(--as-line); margin: 0.8em 0; }
.as-msg-assistant .pi-md table { border-collapse: collapse; margin: 0 0 0.6em; }
.as-msg-assistant .pi-md :is(th, td) { border: 1px solid var(--as-line); padding: 3px 8px; text-align: left; }
.as-msg-cap { font-family: var(--font-ui); font-size: 0.68rem; color: var(--as-danger); margin-top: 4px; }
/* Working = the Helm's incoming TYPING BUBBLE (fg-6% fill, fg-12% border,
 * radius 14) — never bare blinking mono text. The row spans the transcript
 * column; the bubble hugs its text. */
.as-working {
  display: flex;
}
.as-working-bubble {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 60%;
  border-radius: var(--radius-2xl);
  padding: 8px 14px;
  background: color-mix(in srgb, var(--as-fg) 6%, var(--as-bg));
  border: 1px solid color-mix(in srgb, var(--as-fg) 12%, var(--as-bg));
  font-size: 0.78rem;
  color: var(--as-fg-2);
  animation: as-working-blink 2.4s var(--ease-in) infinite;
}
.as-card {
  border: 1px solid var(--as-line);
  border-left: 3px solid var(--as-accent);
  border-radius: var(--radius-md);
  padding: 8px 10px;
  background: var(--as-surface);
}
.as-card-refused,
.as-card-error,
.as-card-unavailable { border-left-color: var(--as-danger); }
/* The in-chat confirmation gate (work item 64): app_act proposed an action
   and is WAITING for the user's answer. Not a failure — a question. Dashed
   accent rail reads as "open", where danger reads as "went wrong". */
.as-card-confirm-required {
  border-left-style: dashed;
  border-left-color: var(--as-accent);
}
.as-card-head {
  display: flex;
  gap: 8px;
  align-items: baseline;
}
.as-card-action {
  font-family: var(--font-data);
  font-size: 0.72rem;
  font-weight: 600;
  color: var(--as-fg);
}
.as-card-outcome {
  font-size: 0.62rem;
  font-family: var(--font-data);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--as-accent);
}
.as-card-refused .as-card-outcome,
.as-card-error .as-card-outcome,
.as-card-unavailable .as-card-outcome { color: var(--as-danger); }
.as-card-ts { margin-left: auto; font-size: 0.66rem; color: var(--as-fg-2); }
.as-card-target {
  margin-top: 2px;
  font-family: var(--font-data);
  font-size: 0.72rem;
  color: var(--as-fg-2);
  overflow-wrap: anywhere;
}
.as-card-detail { margin-top: 4px; font-size: 0.74rem; }
.as-ctx { font-size: 0.68rem; font-family: var(--font-data); color: var(--as-fg-2); }
/* Error card = the Helm chat's tinted, softened treatment: the border is
 * the chat red pulled toward the ground (55%), the fill a 9% wash — never
 * a full-strength danger outline on a bare card. */
.as-errcard {
  border: 1px solid color-mix(in srgb, var(--as-danger) 55%, var(--as-bg));
  background: color-mix(in srgb, var(--as-danger) 9%, var(--as-bg));
  border-radius: var(--radius-lg);
  padding: 10px 12px;
  font-size: 0.78rem;
}
.as-errcard-head {
  font-weight: 700;
  color: var(--as-danger);
  margin-bottom: 2px;
}
.as-errcard-body {
  font-size: 0.92em;
  opacity: 0.8;
  white-space: pre-wrap;
}
/* The Helm transcript column: rows cap at 820px and center (.pi-msgs).
 * Declared LAST so it outranks the per-row widths above at equal
 * specificity. */
.as-msg-row-assistant,
.as-msg-row-user,
.as-working,
.as-card,
.as-errcard,
.as-ctx {
  width: 100%;
  max-width: 820px;
  margin-left: auto;
  margin-right: auto;
}
.as-empty {
  margin: auto;
  padding: 30px 20px;
  text-align: center;
  color: var(--as-fg-2);
  font-size: 0.78rem;
}
.as-confirm {
  padding: 0 14px 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.as-confirm-q {
  border: 1px solid var(--as-accent);
  border-left-width: 3px;
  border-radius: var(--radius-md);
  padding: 8px 10px;
  background: var(--as-surface);
  color: var(--as-fg);
  font-size: 0.8rem;
  font-weight: 600;
}
.as-confirm-note {
  font-size: 0.68rem;
  color: var(--as-fg-2);
  padding-left: 2px;
}
.as-confirm-btns {
  display: flex;
  gap: 6px;
}
.as-queue {
  padding: 0 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-end;
}
.as-queued {
  max-width: 86%;
  border: 1px dashed var(--as-accent);
  border-radius: var(--radius-2xl);
  padding: 6px 12px;
  font-size: 0.78rem;
  opacity: 0.8;
}
/* Glass composer (the Helm's .pi-composer surface): translucent chat
 * ground + blur, hairline top border off the fg ramp; solid fallback when
 * blur is unavailable or transparency is reduced. */
.as-composer {
  position: relative;
  display: flex;
  gap: 8px;
  padding: 10px 14px 14px;
  background: color-mix(in srgb, var(--as-bg) 72%, transparent);
  backdrop-filter: blur(22px) saturate(140%);
  -webkit-backdrop-filter: blur(22px) saturate(140%);
  border-top: 1px solid color-mix(in srgb, var(--as-fg) 8%, transparent);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .as-composer { background: var(--as-bg); }
}
@media (prefers-reduced-transparency: reduce) {
  .as-composer {
    background: var(--as-bg);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}
/* The composer card (the Helm's .pi-card): 22px pill, fg-14% border
 * strengthening to fg-26% on focus, chat caret, fg-42% placeholder — the
 * ring IS the border, not an outline. The pill CLOSES ITS HEIGHT
 * EQUATION: 0.82rem × 1.5 line (19.68px) + 16px vertical padding + 2px
 * border = 37.68px against the 38px min-height, so a single line sits
 * centered (unset line-height ≈1.2 left the text riding ~4px high). */
.as-input {
  flex: 1;
  resize: none;
  min-height: 38px;
  max-height: 120px;
  background: color-mix(in srgb, var(--as-fg) 3%, var(--as-bg));
  color: var(--as-fg);
  caret-color: var(--as-cursor);
  border: 1px solid color-mix(in srgb, var(--as-fg) 14%, var(--as-bg));
  border-radius: 22px;
  padding: 8px 16px;
  font-family: var(--font-ui);
  font-size: 0.82rem;
  line-height: 1.5;
  transition: border-color var(--t-micro) var(--ease-out);
}
.as-input::placeholder { color: color-mix(in srgb, var(--as-fg) 42%, var(--as-bg)); }
.as-input:focus-visible { outline: none; border-color: color-mix(in srgb, var(--as-fg) 26%, var(--as-bg)); }
/* Send rides the dedicated terracotta send accent (--send through the
 * host seat) with white ink, 45% ground-mix when locked — the Helm's
 * .pi-sendbtn. */
/* .as-send's own background/disabled rules retired: it is an additive
   `.ibtn` now and the composer re-points the additive ramp below. */
/* The assistant composer re-points the ADDITIVE ramp to the Claude terracotta
   send seat (--as-send / --as-onsend). The button stays intent-additive; only
   the ramp under it differs, which is what a token family is for — the
   /* alternative was a hand-written `.as-send: colour retired to the button intent ramp (Styles × Themes PR-2); the class survives as a selector hook only. */` overriding the
   system on the one surface with a brand colour. */
.as-composer {
  --btn-additive-fill: var(--as-send);
  --btn-additive-ink: var(--as-onsend);
  --btn-additive-border: var(--as-send);
  --btn-additive-hover-fill: color-mix(in srgb, var(--as-send) 86%, black);
  --btn-additive-hover-ink: var(--as-onsend);
  --btn-additive-hover-border: color-mix(in srgb, var(--as-send) 86%, black);
}
.as-composer-note {
  position: absolute;
  top: -22px;
  left: 14px;
  font-size: 0.68rem;
  color: var(--as-danger);
}
@keyframes as-pop { from { transform: scale(0.4); } to { transform: none; } }
@keyframes as-send-in { from { opacity: 0; transform: translateY(6px) scale(0.98); } to { opacity: 1; transform: none; } }
@keyframes as-receive-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes as-pulse {
  0%, 100% { box-shadow: var(--as-shadow); }
  50% { box-shadow: var(--as-shadow), 0 0 0 6px color-mix(in srgb, var(--as-accent) 30%, transparent); }
}
@keyframes as-spin { to { transform: rotate(360deg); } }
@keyframes as-working-blink { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
/* ---- end assistant overlay structural ---- */

/* ============================================================================
   Onboarding overlays (ob-*) — the CSS twin of the shared engine in
   pi-stack/client/onboarding.ts (same shared-layer split as .bridge-tip):
   the engine is shared, the skin is authored HERE in the console's own
   --hc-* tokens (the console's :root has no floor for the Bridge's raw
   --surface/--ink vocabulary). The scrim's holes are punched by an inline
   `clip-path: path(evenodd, …)` set by the engine; z 1200/1201 sit above
   the console's whole ladder (measured max 1000). */
.ob-scrim {
  position: fixed; inset: 0; z-index: 1200;
  background: var(--hc-scrim);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: ob-enter var(--t-deliberate) var(--ease-out) both;
}
.ob-layer { position: fixed; inset: 0; z-index: 1201; pointer-events: none; }
.ob-card {
  position: fixed; pointer-events: auto; box-sizing: border-box;
  width: 340px; max-width: calc(100vw - 32px);
  background: var(--hc-panel); color: var(--hc-fg);
  border: 1px solid var(--hc-border2); border-radius: var(--hc-radius);
  box-shadow: var(--hc-shadow);
  padding: 14px 16px;
  animation: ob-card-in var(--t-deliberate) var(--ease-out) both;
}
.ob-title { font-weight: 600; font-size: .88rem; margin: 0 0 6px; }
.ob-body { font-size: .82rem; line-height: 1.5; color: var(--hc-ink2); margin: 0 0 12px; }
.ob-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ob-dots { display: flex; gap: 5px; }
.ob-dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: var(--hc-border2); }
.ob-dot.on { background: var(--hc-accent); }
.ob-nav { display: flex; align-items: center; gap: 8px; margin-left: auto; }
/* colour retired to the button intent ramp (Styles × Themes PR-2) */
.ob-btn {
  font: inherit; font-size: .78rem; cursor: pointer; border-radius: var(--radius-sm); padding: 5px 12px;
  transition: background var(--t-micro) var(--ease-out), border-color var(--t-micro) var(--ease-out);
}
/* .ob-btn:hover: colour retired to the button intent ramp (Styles × Themes PR-2); the class survives as a selector hook only. */
/* .ob-btn.ob-primary: colour retired to the button intent ramp (Styles × Themes PR-2); the class survives as a selector hook only. */
/* colour retired to the button intent ramp (Styles × Themes PR-2) */
.ob-skip {
  font: inherit; font-size: .76rem; cursor: pointer; padding: 0; text-decoration: underline; text-underline-offset: 2px;
  transition: color var(--t-micro) var(--ease-out);
}
/* .ob-skip:hover: colour retired to the button intent ramp (Styles × Themes PR-2); the class survives as a selector hook only. */
@keyframes ob-enter {
  from { opacity: 0; backdrop-filter: blur(0); -webkit-backdrop-filter: blur(0); }
  to { opacity: 1; backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); }
}
.ob-card.ob-entrance { animation-delay: 160ms; }
@keyframes ob-card-in {
  from { opacity: 0; transform: translateY(8px) scale(.98); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ob-card,
  .ob-card.ob-entrance,
  .ob-scrim { animation: none; }
}
/* ------------------------------------------------------------------ */
/* API portal page (#/api, item 61 PR 3) — one fenced hunk             */
/* ------------------------------------------------------------------ */
.hc-api .hc-api-scroll { overflow-y: auto; height: 100%; padding: 0 4px 48px; }
.hc-api-section { margin: 20px 0; }
.hc-api-h2 { font-size: 15px; margin: 0 0 8px; text-transform: capitalize; }
.hc-api-p { margin: 6px 0; color: var(--hc-ink2); max-width: 72ch; }
.hc-api-code {
  font-family: var(--font-code); /* R35 */
  font-size: 12px;
  padding: 8px 10px;
  background: var(--hc-surface2); /* the Helm's code-surface fill */
  border: 1px solid var(--hc-line);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  white-space: pre;
  margin: 6px 0;
}
.hc-api-scope { display: flex; gap: 10px; align-items: baseline; margin: 3px 0; }
.hc-api-scopename { font-family: var(--font-code); font-size: 12px; white-space: nowrap; }
.hc-api-scopedesc { color: var(--hc-muted); font-size: 12px; }
.hc-api-op { border: 1px solid var(--hc-line); border-radius: var(--radius-sm); margin: 4px 0; }
.hc-api-op > summary { display: flex; gap: 10px; align-items: center; padding: 6px 10px; cursor: pointer; list-style: none; }
.hc-api-op > summary::-webkit-details-marker { display: none; }
.hc-api-opbody { padding: 2px 10px 10px; }
.hc-api-method { font-size: 11px; font-weight: 700; width: 52px; text-align: center; border-radius: var(--radius-xs); padding: 2px 0; border: 1px solid var(--hc-line); }
.hc-api-path { font-family: var(--font-code); font-size: 12px; flex: 1; }
.hc-api-exlabel { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--hc-muted); margin-top: 8px; }

/* ------------------------------------------------------------------ */
/* seam:bridge-ui-polish (docs/bridge-ui-polish.md) — write ONLY       */
/* inside this anchor. Shared primitives for the admin-surface batch:  */
/* the ONE search box, the in-grid icon action button, the chip-that-  */
/* is-a-button, dd portal + row-subtitle lanes. Tokens only (no bare   */
/* hex — admin-pages.test.mjs refuses); every animated selector is     */
/* registered in THIS section's own reduced-motion guard: a late rule  */
/* of equal specificity would override the early main register, so the */
/* guard must sit BELOW these rules (the onboarding section's idiom).  */
/* ------------------------------------------------------------------ */

/* --- the search box (owner item D): icon + input + conditional clear.
   The wrapper carries the chrome the bare input used to; the interaction
   ladder is: inactive -> hover (border + icon lift) -> focus (accent ring,
   icon accent) -> typing (icon tick per keystroke burst, hc-sb-typing is
   transient by construction — search.ts decays it) -> has-text (the clear
   button EXISTS only now, entering on a spring). --- */
.hc-searchbox {
  position: relative; display: inline-flex; align-items: center; gap: 7px;
  min-width: 260px; padding: 0 6px 0 9px;
  transition: border-color var(--t-micro) var(--ease-out),
              box-shadow var(--t-standard) var(--ease-out);
}
/* An author display beats the UA's [hidden] { display: none } (the
   .rc-menu incident) — the clear button is display:inline-flex. */
.hc-searchbox [hidden] { display: none; }
.hc-searchbox .hc-search { min-width: 0; flex: 1; padding: 6px 0; }
/* inner-outline suppression retired to `.fc-text-input { outline: 0 }` in the fc block (#1262 class B) */
.hc-searchbox .hc-search::-webkit-search-cancel-button {
  -webkit-appearance: none; appearance: none; /* one clear affordance, ours */
}
.hc-sb-ico {
  width: 15px; height: 15px; flex: none;
  transition: transform var(--t-micro) var(--ease-out), color var(--t-micro);
}
.hc-searchbox:hover .hc-sb-ico { transform: scale(1.12); }
.hc-searchbox:focus-within .hc-sb-ico { transform: scale(1.12); }
.hc-searchbox.hc-sb-typing .hc-sb-ico {
  animation: hc-sb-tick var(--t-standard) var(--ease-out);
}
@keyframes hc-sb-tick {
  0% { transform: scale(1.12); }
  50% { transform: scale(.9) rotate(-8deg); }
  100% { transform: scale(1.12); }
}
/* colour retired to the button intent ramp (Styles × Themes PR-2) */
.hc-sb-clear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; padding: 0; border-radius: var(--radius-full); cursor: pointer; flex: none;
  animation: hc-sb-clear-in var(--t-standard) var(--ease-spring);
  transition: transform var(--t-micro) var(--ease-out),
              color var(--t-micro), background var(--t-micro);
}
.hc-sb-clear .icon { width: 13px; height: 13px; }
/* colour retired to the button intent ramp (Styles × Themes PR-2) */
.hc-sb-clear:hover { transform: scale(1.1); }
.hc-sb-clear:active { transform: scale(.85); }
@keyframes hc-sb-clear-in {
  0% { transform: scale(.4); opacity: 0; }
  100% { transform: scale(1); opacity: 1; }
}

/* --- the in-grid icon action button (items B1/C5/F): ONE vocabulary on
   all three admin grids. 28px box (the row fits ~42px), ghost until
   hovered, danger variant for Delete/Disable. The accessible name is the
   old text (aria-label); the tooltip rides data-tip, never title. --- */
.hc-rowbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; margin-right: 4px;
  border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--hc-muted); cursor: pointer; flex: none;
  transition: transform var(--t-micro) var(--ease-out), color var(--t-micro),
              border-color var(--t-micro), background var(--t-micro);
}
.hc-rowbtn .icon { width: 14px; height: 14px; }
.hc-rowbtn:hover { color: var(--hc-fg); border-color: var(--hc-line); background: var(--hc-surface2); }
.hc-rowbtn:active { transform: scale(.9); }
.hc-rowbtn:focus-visible { outline: 2px solid var(--hc-accent); outline-offset: 1px; }
.hc-rowbtn-danger:hover { color: var(--hc-danger); border-color: var(--hc-danger); background: var(--hc-p-red-bg); }
/* Styles × Themes PR-2: the three admin grids' row verbs are .ibtn now, so
   .hc-rowbtn above paints nothing that renders (it stays only as the record
   of the geometry the ramp inherited, and for the reduced-motion guard's
   registry). All the converted rows still need is the gap .hc-rowbtn's
   margin used to give them — LAYOUT ONLY, so buttons-check's paint arm can
   pass it through cls:. It is NOT .hc-action: that class is the old TEXT
   button's, and admin-pages.test pins its absence from users/audit. */
.hc-rowact { margin-right: 4px; }

/* --- the chip-that-is-a-button (item C6): the framework pill's
   affordance grammar — hover brightness, press scale — authored HERE in
   tokens so the framework seam's fence is not written into. Typography
   matches .hc-pill exactly (the item-A geometry lesson). --- */
.hc-chipbtn {
  display: inline-flex; align-items: center; gap: 6px;
  border: 0; padding: 1px 10px; border-radius: var(--radius-lg); cursor: pointer;
  font-size: 12px; line-height: 18px;
  background: var(--hc-surface2); color: var(--hc-fg);
  transition: transform var(--t-micro) var(--ease-out), filter var(--t-micro);
}
.hc-chipbtn:hover { filter: brightness(1.12); }
.hc-chipbtn:active { transform: scale(.96); }
.hc-chipbtn:focus-visible { outline: 2px solid var(--hc-accent); outline-offset: 1px; }

/* --- dropdown extensions (item B2): the portaled pop (dropdown.ts sets
   inline left/top at the anchor's viewport rect), and the two-line row
   face — role name over a muted subtitle. --- */
.hc-dd-pop.fc-pop-portal { z-index: 300; }
.hc-dd-title { display: block; font-weight: 600; }
.hc-dd-sub { display: block; font-size: 11px; color: var(--hc-muted); margin-top: 1px; white-space: normal; max-width: 260px; }
/* The role dropdown IN a users-grid cell (item B2): quiet until engaged —
   the closed face reads as the role pill it replaced; hover, focus or an
   open list reveal the control chrome. Expressed by RE-POINTING the field
   tokens on the host, never by painting the component: the same discipline
   `.scope-gcanon.on` holds for a stateful button. */
.hc-role-dd { --fc-field-bg: transparent; --fc-field-border: transparent; }
.hc-role-dd:hover, .hc-role-dd:focus-within { --fc-field-bg: var(--hc-surface); --fc-field-border: var(--hc-accent); }
.hc-role-dd .hc-dd-btn { padding: 2px 4px; }
.hc-dd-roleopt { display: block; text-align: left; }

/* THIS section's reduced-motion guard (one selector per line, per-arm
   alphabetical — motion-coverage.test.mjs refuses otherwise). */
@media (prefers-reduced-motion: reduce) {
  .hc-sb-clear,
  .hc-searchbox.hc-sb-typing .hc-sb-ico {
    animation: none;
  }
  .hc-chipbtn:active,
  .hc-rowbtn:active,
  .hc-sb-clear:active,
  .hc-sb-clear:hover,
  .hc-searchbox:focus-within .hc-sb-ico,
  .hc-searchbox:hover .hc-sb-ico {
    transform: none;
  }
  .hc-chipbtn,
  .hc-rowbtn,
  .hc-sb-clear,
  .hc-sb-ico,
  .hc-searchbox {
    transition: none;
  }
}
/* ===== end seam:bridge-ui-polish ===== */

/* Help portal access (owner ruling 2026-09-05): the header help icon, top
   right beside the appearance control. Opens the portal in a NEW TAB — the
   portal is a separate document, and help must never navigate a working
   Bridge away. Tokens only. */
/* The help SUBMENU host (owner IA ruling): the circle-help button opens an
   hc-menu listing the two help surfaces. The host is the positioning
   context for the absolute menu, like .hc-account. */
.hc-help-host { position: relative; display: flex; align-items: center; }
/* .hc-helpbtn .icon: retired — the help control rides the .ibtn primitive (S×T W2), which sizes its own glyph. */
.hc-menu-help { min-width: 208px; }

/* ===== Touch input: tap-target sizing (mobile CSS lane, audit Lane A) =====
   The header nav links (~26px) and header icon buttons (30px) carry a >=44px
   hit area on coarse pointers WITHOUT enlarging the glyph/label. Scoped to the
   header so dense grid/dialog icon buttons keep their compact geometry. The
   query is input-modality based (pointer: coarse), so it also lifts touch
   laptops and tablets — OR the phone query (portrait / landscape phone, the
   shared-shell seam's definition), so a phone-shaped viewport carries the
   floor whether or not the pointer reads coarse. Layout/geometry only, no
   color. The @mobile guard asserts this floor HARD (>= 44px). */
@media (pointer: coarse), (max-width: 640px), ((max-width: 960px) and (max-height: 500px)) {
  .hc-brand { min-height: 44px; }
  .hc-navlink { display: inline-flex; align-items: center; min-height: 44px; }
  .hc-header .hc-iconbtn { width: 44px; height: 44px; }
  /* The header's primitive buttons carry the same floor through the size seat (layout only; the ramp keeps the face). */
  /* A 44px touch ring keeps the ring's own glyph rung: raising the ring
     without the glyph is how #hcMore painted an ellipsis at 0.34 of its box
     (measured 2026-09-09 at =<640px and in landscape). */
  .hc-header .ibtn { --ibtn-size: 44px; --ibtn-glyph: 26px; }
}

/* --- Convert AISDLC-2.0 Corpus (#4249, P5 of the plan for #4194) -------
   The toggle row sits in the create form's flow from build like every other
   seat (D4); the epic list grows only on a USER action, never when an async
   seat lands, so the modal does not resize behind the user. Visibility is a
   CLASS here and never [hidden] — home.test.mjs's fixed-size probe walks
   this form and refuses any [hidden] in the flow. */
.hc-create-convertrow {
  display: flex; gap: 8px; align-items: center; margin: 10px 0 0;
}
.hc-create-convertrow .fc-tg { flex: 0 0 auto; }
.hc-convert-label { font-weight: 600; }
.hc-convert-hint { white-space: normal; height: auto; }
.hc-convert-msg {
  margin: 4px 0 0; font-size: 11px; color: var(--hc-muted);
  min-height: 14px; white-space: normal;
}
.hc-convert-epics { display: none; }
.hc-convert-epics.hc-convert-ready {
  display: block; margin-top: 6px; max-height: 180px; overflow-y: auto;
  border: 1px solid var(--hc-line); border-radius: var(--radius-xs); padding: 6px 8px;
  background: var(--hc-surface);
}
.hc-convert-epic {
  display: flex; gap: 8px; align-items: baseline; margin: 2px 0;
}
.hc-convert-epic .fc-tg { display: inline-block; width: auto; margin: 0; }
.hc-convert-epic-name { font-family: var(--font-code); font-size: 12px; }
/* UNSUPPORTED is a VISIBLE word, not a tooltip: a row that merely looks
   unticked is exactly the misread this treatment exists to prevent. */
.hc-convert-unsupported .hc-convert-epic-name { color: var(--hc-muted); }
.hc-convert-epic-why {
  font-size: 11px; color: var(--hc-danger);
  text-transform: uppercase; letter-spacing: 0.04em;
}
