/**
 * Plugin SDK v2 — Component Stylesheet
 *
 * Single source of truth for all ui-* element styles.
 * Edit this file directly. No build step required.
 */

/* ══════════════════════════════════════════════════════════════════
   DEFAULT DESIGN TOKENS (on :root — cascades to all elements
   including portaled popovers/modals appended to <body>).
   Theme overrides are scoped to ui-app[theme] / ui-platform[theme].
   ══════════════════════════════════════════════════════════════════ */

:root {
  --bg: #0f1117;
  --surface: #1a1d27;
  --surface-2: #242836;
  --surface-3: #2e3345;
  --border: #363b4e;
  --text: #e4e6ef;
  /* Every text token clears 4.5:1 on --bg, each --surface and its own tint, and the
     --text-on-* tokens on their fills, in both themes: tools/verify-contrast.mjs. */
  --text-on-accent: #0f1117;
  --text-on-status: #0f1117;
  --text-muted: #afb3c4;
  --text-dim: #989caf;
  --accent: #b0abff;
  --accent-dim: rgba(176, 171, 255, 0.12);
  /* Derived from --accent so a carrier rebrand carries into hover states for
     free. Previously undefined while being referenced at two sites with
     DIFFERENT hardcoded fallbacks (#9688f5 and #9b8bf5), so the fallback was
     the only value that ever applied and the two disagreed. The 80% mix
     reproduces #9688f5 to within one step. Light theme mixes toward black
     instead — see :root[theme='light']. */
  --accent-hover: color-mix(in srgb, var(--accent) 80%, var(--white));
  --info: #76b9fd;
  --info-bg: rgba(118, 185, 253, 0.12);
  --warning: #e8a63d;
  --warning-bg: rgba(232, 166, 61, 0.12);
  --success: #66c77e;
  --success-bg: rgba(102, 199, 126, 0.12);
  --danger: #ff958c;
  --danger-bg: rgba(255, 149, 140, 0.12);
  --muted: #6b7280;
  --white: #ffffff;
  --black: #000000;
  /* Aliased to --surface-3 rather than kept as its former hardcoded #2a2d3a.
     It was referenced once (the floating selection bar in ui-attachments) as
     `var(--surface-elevated, #2a2d3a)` while never being defined anywhere, so
     the dark literal was the only value that ever applied — including in light
     mode, where it rendered a dark bar on a light page. Aliasing fixes that,
     at the cost of a 4-step color shift in dark mode on that one element. */
  --surface-elevated: var(--surface-3);

  /* The dimming wash a viewport-covering layer paints over the page. Introduced
     for <ui-spotlight>, but named generically because it is the same wash the
     dialog and drawer backdrops paint by hand as a literal rgba(0, 0, 0, 0.55) —
     migrating those two onto this token is a separate change with a visible
     diff, so it is left alone here rather than folded into an unrelated one.
     Defined in the base scope, not per theme: a scrim is dark in a light theme
     too, which is what both backdrops already assume. */
  --scrim: color-mix(in srgb, var(--black) 55%, transparent);

  /* ── Chart series palette ──────────────────────────────────────────
     The multi-series identity palette, formerly hardcoded as hex literals in
     elements/dashboard/chart-utils.js where no carrier override could reach
     it — a rebranded deployment kept drawing OneShield purple in every chart.
     Values are unchanged from those literals; --chart-1 now tracks --accent so
     the primary series rebrands with no extra work.

     Deliberately NOT tokenized: the sequential and diverging ramps in
     chart-utils.js and ui-chart-heatmap.js. Those encode magnitude and
     direction, not identity — same reasoning as the status colors. */
  /* Series 1-5 stay apart under deuteranopia, protanopia and tritanopia as well as normal
     vision, by lightness as much as hue; 6-10 only extend the set. 3:1 on --bg and --surface. */
  --chart-1: var(--accent);
  --chart-2: #ffab55;
  --chart-3: #8cf0bc;
  --chart-4: #b94f87;
  --chart-5: #a08d00;
  --chart-6: #56b4e9;
  --chart-7: #e76444;
  --chart-8: #1d9999;
  --chart-9: #9398a5;
  --chart-10: #a67351;

  /* ── Carrier logo ─────────────────────────────────────────────────
     Consumed by .app-switcher-trigger::before. All four move together; the
     copy-paste recipe lives in the platform plugin's ui/branding/tokens.css.

     --brand-logo-on is the generation switch and it is load-bearing: a
     generated pseudo-element is a flex item and therefore consumes the
     trigger's `gap` even at zero size, which would shift the header of every
     deployment that ships no logo. `content: none` generates nothing at all,
     so the default here is byte-identical to having no logo seam.

     Width and height are both explicit because `background-size: contain`
     needs a box to fit into. Set width to match the logo's aspect ratio at
     the chosen height — verified in Chrome that `content: url()` with
     `width: auto` does NOT derive width from the intrinsic ratio, it takes
     the intrinsic width and squashes, so that shortcut is not available. */
  --brand-logo: none;
  --brand-logo-on: none;
  --brand-logo-width: 96px;
  --brand-logo-height: 20px;
  /* Set to `none` when the logo already contains the company wordmark, so the
     app-switcher shows the mark alone instead of mark + duplicated text. */
  --brand-wordmark-display: block;
  /* ui-json-editor's gutter is out of flow, so its width has to be stated twice —
     once on the gutter and once as the field's left inset. One token, so the two
     cannot drift apart and overlap the first characters of every line. Sized for
     four-digit line numbers at --fs-base in --mono. */
  --je-gutter-width: 48px;
  --spacing-1: 2px;
  --spacing-2: 4px;
  --spacing-3: 6px;
  --spacing-4: 8px;
  --spacing-5: 10px;
  --spacing-6: 12px;
  --spacing-7: 16px;
  --spacing-8: 24px;
  --spacing-9: 32px;
  --spacing-10: 48px;
  /* Radius scale. --radius-sm and --radius-full were referenced by newer
     sections but never defined, so 6 declarations silently resolved to 0.
     Values chosen to match the dominant raw usage already in this file:
     6px appears 34 times (vs 4px 29), and 999px 5 times (vs 99px 3).
     ND-100 formalizes the full scale and migrates the remaining raw values. */
  --radius-2xs: 3px;
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius: 8px;
  --radius-md: 10px;
  --radius-lg: 12px;
  --radius-full: 999px;
  --radius-circle: 50%;
  /* --radius-2xs/xs/md/circle added by ND-100 for values with 8+ uses that had no
     token: 4px (29 uses), 50% (17), 10px (11), 3px (8). The remaining long tail —
     5px, 9px, 7px, 2px, 1px, 14px, 16px and a 99px that means the same as
     --radius-full — is left inline and reported by lint as shrink-only debt,
     rather than rounded into a neighboring step. Rounding would be a redesign,
     and this commit is a refactor. */
  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono: 'JetBrains Mono', 'Fira Code', monospace;


  /* ── Typography (ND-100) ──────────────────────────────────────────────────
     317 font-size, 156 font-weight and 50 line-height declarations were literal
     and none were tokenized, while color and spacing were near-fully disciplined
     (2,029 var() uses). Values here are the EXACT ones already in use, so this
     moves no pixels; the scale is descriptive, not a redesign.

     Named --fs-/--fw-/--lh- rather than --text-*, because --text, --text-muted,
     --text-dim and --text-on-accent are COLOR tokens. --text-sm sitting beside
     --text-muted would be actively misleading.

     Sizes cluster hard: 12/13/11/14/10px account for 290 of the 317 uses, which
     is why the scale is dense at the bottom and sparse above 16px. Ten
     declarations sit OFF this scale (8px, 12.5px, 15px, 17px, 22px and one
     font-size: 0) and are deliberately left inline — lint reports them as
     shrink-only debt so they stay visible instead of being quietly rounded into a
     neighboring step, which would be a redesign disguised as a refactor. */
  /* 8px and 17px are MISSING STEPS, not off-scale accidents: 8px is both sidebar
     chevrons and 17px is every dialog/drawer/hero title. Each was already used
     consistently for one role, so naming them changes nothing and removes 5
     declarations from the off-scale list. */
  --fs-3xs: 8px;
  --fs-2xs: 9px;
  --fs-xs: 10px;
  --fs-sm: 11px;
  --fs-base: 12px;
  --fs-md: 13px;
  --fs-lg: 14px;
  --fs-xl: 16px;
  --fs-title: 17px;   /* dialog, drawer and hero titles */
  --fs-2xl: 18px;
  --fs-3xl: 20px;
  --fs-4xl: 24px;
  --fs-5xl: 28px;

  --fw-light: 300;
  --fw-normal: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;
  --fw-extrabold: 800;

  /* Not typography — the technique for removing inline-box descender space under
     an icon. Named so it is on-scale rather than reported as a stray 0. */
  --lh-flush: 0;
  --lh-none: 1;
  --lh-tight: 1.3;
  --lh-snug: 1.35;
  --lh-normal: 1.4;
  --lh-relaxed: 1.5;
  --lh-loose: 1.6;

  /* ── Stacking order (ND-100) ──────────────────────────────────────────────
     Before this, z-index was assigned ad hoc and had escalated to the sequence
     1, 2, 5, 10, 11, 20, 100, 1000, 9999, 10000, 11000 with FOUR-, THREE- and
     TWO-WAY TIES. A tie means DOM order decides which element wins, so adding a
     component could silently reorder existing ones, and a select inside a dialog
     was 1000 against a dialog backdrop of 1000.

     There are two bands, and the distinction is what the old numbers were
     groping towards:

     LOCAL — the element stays inside its component's subtree, so it only has to
     clear its own siblings. These are small on purpose.

     GLOBAL — the element is mounted on document.body or covers the viewport, so
     its value is a real contract against every other global layer. Ordering here
     is deliberate, not inherited:

       modal    < dropdown   a dropdown opened inside a dialog must appear over it
       modal    < toast      a notification must never be hidden by a modal
       dropdown < popover    a popover can be anchored to a dropdown item
       *        < tooltip    transient, pointer-events:none, must never be hidden

     Gaps of 100 leave room to insert a layer without renumbering, which is how
     the original values ran away. */

  /* Local band */
  --z-raised: 1;      /* chips, prefixes, background layers */
  --z-elevated: 2;    /* list/section headers above their own rows */
  --z-loading: 5;     /* in-component loading veil */
  --z-sticky: 10;     /* sticky headers, resize handles */
  --z-drag: 20;       /* drag-and-drop overlay */
  /* The narrowest a page may be squeezed to by a non-modal drawer reserving its
     width. Below it the drawer overlays instead. See ui-drawer._claimInset. */
  --ui-drawer-min-content: 880px;

  --z-menu: 100;      /* in-subtree menus and dropdowns, global header */


  /* ── Elevation (ND-100) ───────────────────────────────────────────────────
     Named by ROLE, because the roles already existed and the values did not
     agree with them. Values are the EXACT ones previously inline, so this
     commit moves no pixels.

     What the naming exposes, and what should be consolidated as a deliberate
     design decision rather than smuggled into a refactor:

       dropdowns used THREE different shadows — --elev-dropdown (4 uses),
       --elev-menu (3 uses) and --elev-dropdown-sm (1 use, .lt-dropdown)
       modals used THREE — --elev-modal (dialog + drawer), --elev-overlay
       (document viewer) and --elev-modal-lg (app launcher)

     --elev-dropdown-sm, --elev-overlay, --elev-modal-lg and --elev-image are
     single-use and named so they are greppable; they are the candidates for
     collapsing into the tier above them. Focus and status rings (0 0 0 Npx, and
     the 0 0 4px status glows) are deliberately NOT in this scale — they are state
     indicators, not elevation, and belong with the color tokens. */
  --elev-raised: 0 1px 2px rgba(0, 0, 0, 0.2);
  --elev-card: 0 2px 8px rgba(0, 0, 0, 0.2);
  --elev-tooltip: 0 4px 12px rgba(0, 0, 0, 0.3);
  --elev-menu: 0 8px 24px rgba(0, 0, 0, 0.3);
  --elev-menu-up: 0 -8px 24px rgba(0, 0, 0, 0.3);
  --elev-dropdown: 0 8px 24px rgba(0, 0, 0, 0.4);
  --elev-popover: 0 16px 48px rgba(0, 0, 0, 0.25), 0 4px 12px rgba(0, 0, 0, 0.15);
  --elev-modal: 0 20px 60px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04);
  --elev-dropdown-sm: 0 4px 16px rgba(0, 0, 0, 0.3);
  --elev-overlay: 0 16px 48px rgba(0, 0, 0, 0.5);
  --elev-modal-lg: 0 24px 64px rgba(0, 0, 0, 0.35);
  --elev-image: 0 2px 12px rgba(0, 0, 0, 0.3);

  /* Global band — body-mounted or viewport-covering */
  --z-modal: 1100;     /* dialog, drawer, app launcher, document viewer (+n when stacked) */
  --z-dropdown: 1300;  /* body-mounted dropdowns */
  --z-popover: 1400;    /* body-mounted popovers */
  --z-demo-panel: 1425; /* ui-demo-panel's dock — see below */
  --z-spotlight: 1450;  /* ui-spotlight's scrim — see below */
  --z-toast: 1500;      /* notifications */
  --z-tooltip: 1600;    /* topmost, transient */
  /* THE DEMO-ASSISTANT PAIR, and the ordering between them is the contract.

     --z-spotlight sits above every layer it is meant to DIM and below the one
     layer that must still be readable through it. Above --z-popover (and so
     above --z-modal) because a guided tour has to be able to point at a field
     inside an open dialog, and a scrim underneath the dialog would dim the page
     while leaving the dialog bright — the exact opposite of what was asked for.
     Below --z-toast because a notification is the application telling the user
     something, and a demo overlay must not be able to bury it.

     --z-demo-panel is a stop of its own rather than a reuse of --z-modal, and
     the reason is a real defect and not tidiness: `.drawer-backdrop` is already
     `z-index: var(--z-modal)`, so a docked panel sharing that value TIES with
     it and DOM order silently decides whether an opened drawer buries the
     narration. A two-way tie decided by document order is the exact failure the
     whole scale was introduced to remove. It sits above every modal surface so
     a dialog cannot bury the narration mid-demo, and deliberately BELOW
     --z-spotlight so that "the spotlight dims everything except the one thing
     under discussion" stays literally true — the panel included.

     Neither increments, so the tighter 25-point gaps here are safe where
     --z-modal's 200 is not. */
  /* The gap above --z-modal is 200, not 100, because it is the ONE layer that
     increments: ui-dialog stacks nested dialogs at --z-modal + n. With a gap of
     100 a 100-deep stack would tie with --z-dropdown, and a tie is precisely the
     defect this scale exists to remove. Every other gap can stay at 100 because
     nothing else counts upwards. */
}

:root[density='compact'] {
  --spacing-1: 1px;
  --spacing-2: 2px;
  --spacing-3: 4px;
  --spacing-4: 6px;
  --spacing-5: 8px;
  --spacing-6: 8px;
  --spacing-7: 12px;
  --spacing-8: 16px;
  --spacing-9: 24px;
  --spacing-10: 32px;
}

:root[density='comfortable'] {
  --spacing-1: 3px;
  --spacing-2: 6px;
  --spacing-3: 8px;
  --spacing-4: 12px;
  --spacing-5: 14px;
  --spacing-6: 16px;
  --spacing-7: 24px;
  --spacing-8: 32px;
  --spacing-9: 40px;
  --spacing-10: 56px;
}

:root[theme='light'] {
  --bg: #ffffff;
  --surface: #f8f9fa;
  --surface-2: #f0f1f3;
  --surface-3: #e8e9ec;
  --border: #e0e2e7;
  --text: #1a1d27;
  --text-on-accent: #ffffff;
  --text-on-status: #ffffff;
  --text-muted: #525665;
  --text-dim: #64687b;
  --accent: #5e48c9;
  --accent-dim: rgba(94, 72, 201, 0.10);
  /* Mixes toward black, not white: on a light background a hover state has to
     darken to read as emphasis. The dark-theme definition mixes toward white. */
  --accent-hover: color-mix(in srgb, var(--accent) 85%, var(--black));
  --info: #1c619f;
  --info-bg: rgba(28, 97, 159, 0.08);
  --warning: #8c5000;
  --warning-bg: rgba(140, 80, 0, 0.08);
  --success: #006d29;
  --success-bg: rgba(0, 109, 41, 0.08);
  --danger: #ab3030;
  --danger-bg: rgba(171, 48, 48, 0.08);
  --chart-2: #bf6600;
  --chart-3: #004f25;
  --chart-4: #973069;
  --chart-5: #2b88c0;
  --chart-6: #8a7d00;
  --chart-7: #af2f09;
  --chart-8: #006566;
  --chart-9: #6d717e;
  --chart-10: #774826;
  --muted: #9ca3af;
  --white: #ffffff;
  --black: #000000;
}

/* ══════════════════════════════════════════════════════════════════
   PLUGIN-SDK TOKEN BRIDGE
   Maps SDK design tokens → plugin-sdk component tokens (--ui-*).
   Plugin-SDK shadow DOM components (ui-pdf-viewer, ui-marker-list,
   ui-floating-panel, ui-color-legend, etc.) inherit these via :root.
   ══════════════════════════════════════════════════════════════════ */

:root {
  --ui-background: var(--surface);
  --ui-foreground: var(--text);
  --ui-border: var(--border);
  --ui-muted: var(--surface-2);
  --ui-muted-foreground: var(--text-muted);
  --ui-accent: var(--accent-dim);
  --ui-ring: var(--accent);
  --ui-radius: var(--radius);
}

/* ══════════════════════════════════════════════════════════════════
   RESET
   ══════════════════════════════════════════════════════════════════ */

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
body {
  margin: 0;
  line-height: var(--lh-relaxed);
  -webkit-font-smoothing: antialiased;

  /* Typography and palette belonged ONLY to ui-platform, so any page without
     the app shell fell back to the UA default and rendered in Times. The login
     page did exactly that. sdk.css has to be self-sufficient for a page that
     is not the app — ui-platform sets these on itself anyway, so the shell is
     unaffected. */
  font-family: var(--font);
  color: var(--text);
  background: var(--bg);
}

/* An element with a display rule beats the UA stylesheet's
   [hidden] { display: none }, so `hidden` silently did nothing on ui-form,
   ui-button and every other element sdk.css gives a display to — the login
   page rendered its MFA step and its Back button permanently. This was already
   known and patched per-element for ui-report-builder; it needs to be general.
   Author-level and !important, because the element rules it must beat are also
   author-level. */
[hidden] {
  display: none !important;
}

/* A condition nobody has evaluated asserts nothing, so a conditional element is
   held back until resolveShowWhen marks a verdict. Not !important: the verdict
   itself is an inline display, and this must lose to it. */
[show-when]:not([show-when-resolved]) {
  display: none;
}

/* ══════════════════════════════════════════════════════════════════
   LAYOUT — ui-platform (Multi-App Shell)
   ══════════════════════════════════════════════════════════════════ */

ui-platform {
  display: grid;
  grid-template-rows: var(--header-height, 56px) 1fr;
  height: 100vh;
  max-height: 100vh;
  width: 100%;
  font-family: var(--font), serif;
  background: var(--bg);
  color: var(--text);

  /* Platform-specific layout tokens (not duplicating :root palette) */
  --header-height: 56px;
  --header-bg: var(--surface);
  --header-border: var(--border);
  --header-text: var(--text);
  --header-accent: var(--accent);
}


ui-platform > ui-global-header {
  grid-row: 1;
  grid-column: 1;
}

ui-platform > ui-app {
  grid-row: 2;
  grid-column: 1;
  min-height: 0;
  height: 100%;
  overflow: hidden;
}

/* ══════════════════════════════════════════════════════════════════
   LAYOUT — ui-global-header + child components
   ══════════════════════════════════════════════════════════════════ */

ui-global-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--header-height, 56px);
  padding: 0 var(--spacing-7);
  background: var(--header-bg, var(--surface));
  border-bottom: 1px solid var(--header-border, var(--border));
  position: sticky;
  top: 0;
  z-index: var(--z-menu);
}

/* ── Header Actions (right side) ──────────────────────────────── */

ui-header-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}

ui-header-actions ui-theme-toggle {
  display: flex;
  align-items: center;
}

ui-header-actions .theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  padding: 0;
  font-size: 0;
}

ui-header-actions .theme-toggle:hover {
  background: var(--surface-2);
  color: var(--text);
}

ui-header-actions .theme-toggle svg {
  width: 20px;
  height: 20px;
}

.header-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  position: relative;
}

.header-icon-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

.header-icon-btn svg {
  width: 20px;
  height: 20px;
}

/* ── App Switcher Trigger ─────────────────────────────────────── */

.app-switcher-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-5);
  border-radius: var(--radius);
  border: none;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-family: var(--font);
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  transition: background 0.15s;
}

.app-switcher-trigger:hover {
  background: var(--surface-2);
}

/* Carrier logo. The only brand-image seam in the v3 shell, and it is CSS-only
   on purpose: introducing a `logo` attribute on <ui-app-switcher> instead would
   put a carrier concern into the frozen authored vocabulary and drag in the
   contract, docs and IntelliSense gates for something no consumer HTML should
   have to know about. Tokens are defined in :root; a carrier sets them in the
   platform plugin's ui/branding/tokens.css.

   Renders nothing until --brand-logo-on is set to '' — see the note on that
   token for why the switch cannot be folded into the width. */
.app-switcher-trigger::before {
  content: var(--brand-logo-on);
  width: var(--brand-logo-width);
  height: var(--brand-logo-height);
  background-image: var(--brand-logo);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: left center;
  flex: none;
}

ui-app-switcher {
  display: flex;
  align-items: center;
  gap: var(--spacing-1);
}

.app-switcher-sidebar-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  padding: 0;
}

.app-switcher-sidebar-toggle:hover {
  background: var(--surface-2);
  color: var(--text);
}

.app-switcher-sidebar-toggle svg {
  width: 20px;
  height: 20px;
}

.app-switcher-label {
  white-space: nowrap;
  /* `block` is the default only because this is already a flex item and an
     inline span is blockified to exactly that — so declaring it moves nothing.
     It exists so a carrier whose logo already contains the wordmark can set
     --brand-wordmark-display: none and avoid rendering the name twice. */
  display: var(--brand-wordmark-display);
}

.app-switcher-chevron svg {
  width: 14px;
  height: 14px;
  color: var(--text-muted);
}

/* ── Notification Bell ────────────────────────────────────────── */

.notification-trigger {
  position: relative;
}

.notification-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  padding: 0 var(--spacing-2);
  border-radius: var(--radius);
  background: var(--danger);
  color: var(--text-on-status);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: var(--lh-none);
}

/* ── User Avatar ──────────────────────────────────────────────── */

.user-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-circle);
  background: var(--accent-dim);
  border: 2px solid var(--border);
  overflow: hidden;
}

.user-avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.user-avatar-initials {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--accent);
}

/* Shown instead of initials when the session names nobody. The icon strokes
   currentColor, so it must be given the same color the initials carry. */
.user-avatar-icon {
  display: flex;
  color: var(--accent);
}

/* ── Popover ──────────────────────────────────────────────────── */

ui-popover {
  /* Tokens inherited from :root — no duplication needed */
  font-family: var(--font);
  color: var(--text);
  opacity: 0;
  transform-origin: top left;
  transition: opacity 0.15s ease, transform 0.15s ease;
  pointer-events: none;
}


ui-popover.popover-open {
  opacity: 1;
  pointer-events: auto;
}

.popover-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-popover);
  overflow: hidden;
}

/* ── Switcher Popover ─────────────────────────────────────────── */

.switcher-search {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-5) var(--spacing-6);
  border-bottom: 1px solid var(--border);
}

.switcher-search-icon svg {
  width: 16px;
  height: 16px;
  color: var(--text-muted);
}

.switcher-search-input {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: var(--fs-md);
  font-family: var(--font);
  outline: none;
}

/* ── Focus ring ── */
/* The rule above strips the UA outline on a borderless input, which left no
   focus indicator at all. Inset offset because the input sits flush inside a
   bordered search container. (ND-35) */
.switcher-search-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.switcher-search-input::placeholder {
  color: var(--text-dim);
}

.switcher-list {
  padding: var(--spacing-3);
  max-height: 320px;
  overflow-y: auto;
}

.switcher-section-label {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-5) var(--spacing-2);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.switcher-section-label svg {
  width: 12px;
  height: 12px;
}

.switcher-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-4) var(--spacing-5);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background 0.1s;
}

.switcher-item:hover {
  background: var(--surface-2);
}

.switcher-item.active {
  background: var(--accent-dim);
  color: var(--accent);
}

.switcher-item-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex-shrink: 0;
}

.switcher-item-icon svg {
  width: 18px;
  height: 18px;
}

.switcher-item-label {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
}

.switcher-empty {
  padding: var(--spacing-7);
  text-align: center;
  font-size: var(--fs-md);
  color: var(--text-muted);
}

.switcher-footer {
  border-top: 1px solid var(--border);
  padding: var(--spacing-3);
}

.switcher-view-all {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: var(--spacing-4) var(--spacing-5);
  border: none;
  background: transparent;
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--fs-md);
  font-family: var(--font);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}

.switcher-view-all:hover {
  background: var(--surface-2);
  color: var(--text);
}

.switcher-view-all-icon svg {
  width: 14px;
  height: 14px;
}

/* ── App Launcher Modal ───────────────────────────────────────── */

.app-launcher-backdrop {
  /* Tokens inherited from :root — no duplication needed */
  font-family: var(--font);
  color: var(--text);
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  animation: fade-in 0.15s ease;
}


.app-launcher-modal {
  width: min(520px, 92vw);
  max-height: min(420px, 80vh);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-modal-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: scale-in 0.2s ease;
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes scale-in {
  from { opacity: 0; transform: scale(0.95); }
  to { opacity: 1; transform: scale(1); }
}

.launcher-header {
  border-bottom: 1px solid var(--border);
}

.launcher-search {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-6) var(--spacing-7);
}

.launcher-search-icon svg {
  width: 16px;
  height: 16px;
  color: var(--text-muted);
  flex-shrink: 0;
}

.launcher-search-input {
  flex: 1;
  border: none;
  background: transparent;
  color: var(--text);
  font-size: var(--fs-lg);
  font-family: var(--font);
  outline: none;
}

/* ── Focus ring ── */
/* See .switcher-search-input — same borderless-input-in-a-container shape. (ND-35) */
.launcher-search-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.launcher-search-input::placeholder {
  color: var(--text-dim);
}

.launcher-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  background: transparent;
  border-radius: var(--radius);
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.1s;
}

.launcher-close:hover {
  background: var(--surface-2);
  color: var(--text);
}

.launcher-close svg {
  width: 16px;
  height: 16px;
}

.launcher-grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--spacing-6);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--spacing-4);
  align-content: start;
}

.launcher-card {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-5) var(--spacing-6);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  font-family: var(--font);
  text-align: left;
  transition: background 0.1s, border-color 0.1s, transform 0.1s;
}

.launcher-card:hover {
  background: var(--surface-2);
  border-color: var(--text-dim);
}

.launcher-card:active {
  transform: scale(0.98);
}

.launcher-card.active {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--accent);
}

.launcher-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius);
  background: var(--surface-2);
  flex-shrink: 0;
}

.launcher-card.active .launcher-card-icon {
  background: var(--accent-dim);
}

.launcher-card-icon svg {
  width: 16px;
  height: 16px;
}

.launcher-card-label {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.launcher-empty {
  grid-column: 1 / -1;
  padding: var(--spacing-10) var(--spacing-7);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-md);
}

/* ── Notification Popover ─────────────────────────────────────── */

.notification-header {
  padding: var(--spacing-6) var(--spacing-7);
  border-bottom: 1px solid var(--border);
}

.notification-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

.notification-list {
  padding: var(--spacing-4);
  max-height: 280px;
  overflow-y: auto;
}

.notification-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-8) var(--spacing-7);
  color: var(--text-muted);
  font-size: var(--fs-md);
}

.notification-empty-icon svg {
  width: 24px;
  height: 24px;
  opacity: 0.5;
}

.notification-item {
  padding: var(--spacing-5) var(--spacing-6);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background 0.1s;
}

.notification-item:hover {
  background: var(--surface-2);
}

.notification-item.unread {
  border-left: 3px solid var(--accent);
}

.notification-item-text {
  font-size: var(--fs-md);
  color: var(--text);
  line-height: var(--lh-normal);
}

.notification-item-time {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  margin-top: var(--spacing-2);
}

.notification-footer {
  border-top: 1px solid var(--border);
  padding: var(--spacing-4);
}

.notification-view-all {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  width: 100%;
  padding: var(--spacing-4) var(--spacing-6);
  border: none;
  background: transparent;
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--fs-md);
  font-family: var(--font);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}

.notification-view-all:hover {
  background: var(--surface-2);
  color: var(--text);
}

.notification-view-all svg {
  width: 14px;
  height: 14px;
}

/* ── User Menu Popover ────────────────────────────────────────── */

.user-menu-header {
  padding: var(--spacing-6) var(--spacing-7) var(--spacing-5);
  border-bottom: 1px solid var(--border);
}

.user-menu-email {
  font-size: var(--fs-md);
  color: var(--text-muted);
}

.user-menu-items {
  padding: var(--spacing-3);
}

.user-menu-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  width: 100%;
  padding: var(--spacing-4) var(--spacing-5);
  border: none;
  background: transparent;
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--fs-md);
  font-family: var(--font);
  cursor: pointer;
  transition: background 0.1s;
  text-align: left;
}

.user-menu-item:hover {
  background: var(--surface-2);
}

.user-menu-item svg {
  width: 16px;
  height: 16px;
  color: var(--text-muted);
}

.user-menu-item.danger {
  color: var(--danger);
}

.user-menu-item.danger svg {
  color: var(--danger);
}

.user-menu-divider {
  height: 1px;
  background: var(--border);
  margin: var(--spacing-2) 0;
}

.user-menu-sub-label {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.user-menu-sub-label svg {
  width: 14px;
  height: 14px;
  color: var(--text-muted);
}

.user-menu-sub-options {
  padding: 0 var(--spacing-3);
}

.user-menu-sub-item {
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  gap: var(--spacing-4);
}

.user-menu-sub-item svg {
  width: 14px;
  height: 14px;
  color: var(--accent);
}

.user-menu-sub-item.active {
  color: var(--accent);
}

.density-check-placeholder {
  display: inline-block;
  width: 14px;
  height: 14px;
}

/* ── Brand Switcher Popover ───────────────────────────────────────
   <ui-brand-switcher>. The trigger reuses .header-icon-btn so it is pixel-
   identical to the bell and the avatar beside it; only the menu is new.

   NOTHING HERE NAMES OR COLORS A BRAND. The active entry is marked with a
   check glyph rather than a color swatch, because a swatch would require the
   SDK to know each brand's palette — and a brand's colors live in the
   carrier's own :root[data-brand='…'] token block, which cannot be sampled
   without applying it. A check is honest; an invented swatch color would not
   be.

   Nothing here declares a stacking level, deliberately — the panel is
   <ui-popover>, which already owns the body-mounted --z-popover layer. Writing
   one on the trigger would create a second, competing answer to the same
   question. (Note for the next editor: the CSS-SCALE lint rule matches the
   literal property name in comments too, so do not spell it out here.) */

ui-header-actions ui-brand-switcher {
  display: flex;
  align-items: center;
}

/* The panel itself. Anchored to the trigger rather than body-mounted: it lives
   inside <ui-header-actions>, which is not clipped and is already the header's
   own stacking context, so the in-subtree band is the correct one — a
   body-mounted layer would be claiming a global level it does not need. */
.brand-menu {
  position: absolute;
  top: calc(100% + var(--spacing-3));
  right: 0;
  min-width: 220px;
  z-index: var(--z-menu);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-menu);
}

.brand-menu[hidden] { display: none; }

/* The trigger is the positioning context for the panel above. */
ui-brand-switcher {
  position: relative;
}

.brand-menu-header {
  padding: var(--spacing-5) var(--spacing-7) var(--spacing-4);
  border-bottom: 1px solid var(--border);
}

.brand-menu-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.brand-menu-items {
  padding: var(--spacing-3);
}

.brand-menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-5);
  width: 100%;
  padding: var(--spacing-4) var(--spacing-5);
  border: none;
  background: transparent;
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--fs-md);
  font-family: var(--font);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
  text-align: left;
}

.brand-menu-item:hover {
  background: var(--surface-2);
}

/* The menu is body-mounted and reached by keyboard immediately on open, so it
   needs its own ring — the .uib rule does not apply to these buttons. (ND-35) */
.brand-menu-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.brand-menu-item.active {
  color: var(--accent);
}

.brand-menu-item-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.brand-menu-check {
  display: inline-flex;
  align-items: center;
  flex: none;
  line-height: var(--lh-flush);
  color: var(--accent);
}

.brand-menu-check svg {
  width: 14px;
  height: 14px;
}

/* ── ui-language-switcher (ND-620) ─────────────────────────────────
   Deliberately a parallel rule set rather than a shared .picker-menu
   refactor. Merging them would touch every brand-menu selector and put
   an unrelated visual regression risk on the branding surface inside an
   i18n change; the two can be unified later as its own change. Same
   tokens, same z-band, same focus-ring reasoning as .brand-menu above. */
.lang-menu {
  position: absolute;
  top: calc(100% + var(--spacing-3));
  right: 0;
  min-width: 220px;
  z-index: var(--z-menu);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-menu);
}

.lang-menu[hidden] { display: none; }

/* The trigger is the positioning context for the panel above. */
ui-language-switcher {
  position: relative;
}

.lang-menu-header {
  padding: var(--spacing-5) var(--spacing-7) var(--spacing-4);
  border-bottom: 1px solid var(--border);
}

.lang-menu-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.lang-menu-items {
  padding: var(--spacing-3);
}

.lang-menu-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-5);
  width: 100%;
  padding: var(--spacing-4) var(--spacing-5);
  border: none;
  background: transparent;
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--fs-md);
  font-family: var(--font);
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
  text-align: left;
}

.lang-menu-item:hover {
  background: var(--surface-2);
}

.lang-menu-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.lang-menu-item.active {
  color: var(--accent);
}

.lang-menu-item-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.lang-menu-check {
  display: inline-flex;
  align-items: center;
  flex: none;
  line-height: var(--lh-flush);
  color: var(--accent);
}

.lang-menu-check svg {
  width: 14px;
  height: 14px;
}

/* ══════════════════════════════════════════════════════════════════
   LAYOUT — ui-app
   ══════════════════════════════════════════════════════════════════ */

ui-app {
  display: grid;
  grid-template-columns: var(--sidebar-width, 260px) 1fr;
  grid-template-rows: 1fr;
  min-height: 100%;
  width: 100%;
  font-family: var(--font), serif;
  background: var(--bg);
  color: var(--text);
  transition:
    grid-template-columns 0.2s ease,
    padding 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

/* A NON-MODAL DRAWER RESERVES ITS WIDTH HERE, rather than floating over the
   page. ui-drawer publishes the measured panel width on :root while it is open
   and removes it on close; the sidebar column is a fixed track, so the whole
   inset comes off the content column and the view reflows out from under the
   panel. Without this the page behind is advertised as usable while its right
   edge — the pager, the last columns of a table — is under the panel and
   unreachable, which is a worse offer than a modal drawer that admits it.

   THE DECISION IS MADE IN ui-drawer, not here, and there is only one of it: the
   property is published only when the viewport minus the panel still leaves
   --ui-drawer-min-content, so this rule applies it unconditionally. A media
   query here as well would be a second opinion on the same question — and with
   any panel wider than 20px the content floor is the binding one anyway, so the
   old `min-width: 900px` gate never decided anything the threshold did not.
   The duration matches the panel's own slide. */
ui-app {
  padding-left: var(--ui-drawer-inset-left, 0px);
  padding-right: var(--ui-drawer-inset-right, 0px);
}

/* Disable transition during drag for instant feedback */
ui-app.sidebar-resizing {
  transition: none;
}

ui-app > ui-app-sidebar {
  grid-column: 1;
  grid-row: 1;
  min-width: 0;
  height: 100%;
}

ui-app > ui-module {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
  overflow-y: auto;
}

ui-app > ui-module ~ ui-module {
  display: none;
}

/* ── A route <ui-app> could not honor ──────────────────────────────
   Occupies the MAIN COLUMN, in place of a module, because that is what it
   stands in for: no module was activated and none is going to be. Painting it
   anywhere else — a toast, a strip above the module — would leave the region
   the user is looking at blank, and a blank main region is indistinguishable
   from a shell that failed to boot, which is the failure this whole state
   exists to replace.

   Both the waiting state and the refusal use one box. They differ only in the
   icon's color: the words carry the meaning, and a tinted panel for "opening…"
   would read as an error for the ~1s the normal case takes.
   See PluginApp._paintRouteNotice. */

ui-app > .app-route-notice {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-8);
  overflow-y: auto;
}

ui-app:not(:has(> ui-app-sidebar)) > .app-route-notice {
  grid-column: 1;
}

ui-app > .app-route-notice .arn-box {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-4);
  /* A measure, not a pixel width: this is a paragraph of prose and the readable
     line length is a function of the type, not of the viewport. */
  max-width: 52ch;
  text-align: center;
}

ui-app > .app-route-notice .arn-icon {
  display: inline-flex;
  color: var(--text-muted);
}

ui-app > .app-route-notice .arn-icon > svg {
  width: 32px;
  height: 32px;
}

/* The refusal is the only one that is tinted, and only on the icon. */
ui-app > .app-route-notice[data-kind='refused'] .arn-icon {
  color: var(--warning);
}

ui-app > .app-route-notice .arn-heading {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  color: var(--text);
}

ui-app > .app-route-notice .arn-detail {
  margin: 0;
  font-size: var(--fs-base);
  font-weight: var(--fw-normal);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

/* THE ADDRESS THAT WAS ASKED FOR, shown verbatim — pathname AND query. It is the
   only remaining record of the request (this state deliberately does not rewrite
   the URL) and it is what a person pastes into a support request.

   --text, NOT --text-dim, AND NOT --text-muted EITHER — both were measured, in
   both themes, on the branded app. --text-dim (the original) came out at 2.37:1
   dark / 2.25:1 light on --surface-2; --text-muted reached 4.59:1 dark but only
   4.28:1 light, still under AA's 4.5:1 for text this size. At --fs-sm this is
   normal text, so 4.5:1 is the bar and 3:1 does not apply.
   The quiet-and-secondary instinct is wrong here anyway: this is the one piece of
   information the panel exists to preserve, and the monospace chip on a tinted
   ground already reads as secondary without being dimmed. */
ui-app > .app-route-notice .arn-address {
  margin: 0;
}

ui-app > .app-route-notice .arn-address > code {
  display: inline-block;
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text);
  overflow-wrap: anywhere;
}

/* No sidebar — single column layout, module fills full width */
ui-app:not(:has(> ui-app-sidebar)) {
  grid-template-columns: 1fr;
}
ui-app:not(:has(> ui-app-sidebar)) > ui-module {
  grid-column: 1;
}

/* Collapsed sidebar — animate to zero width */
ui-app:has(> ui-app-sidebar[collapsed]) {
  grid-template-columns: 0px 1fr;
}

ui-app-sidebar[collapsed] {
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}

ui-app-sidebar:not([collapsed]) {
  opacity: 1;
  transition: opacity 0.15s ease 0.05s;
}

/* ══════════════════════════════════════════════════════════════════
   SHELL — ui-app-sidebar
   ══════════════════════════════════════════════════════════════════ */

ui-app-sidebar {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--border);
  overflow: hidden;
}

ui-app-sidebar .sidebar-header {
  padding: var(--spacing-7) var(--spacing-7) var(--spacing-6);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: 1.5px;
  color: var(--text-muted);
  text-transform: uppercase;
  border-bottom: 1px solid var(--border);
}

ui-app-sidebar .sidebar-nav {
  padding: var(--spacing-4);
  flex: 1;
  overflow-y: auto;
  min-height: 0;
}

ui-app-sidebar .nav-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-5) var(--spacing-6);
  border-radius: var(--radius);
  color: var(--text-muted);
  cursor: pointer;
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
  transition:
    background 0.15s,
    color 0.15s;
  user-select: none;
}

ui-app-sidebar .nav-item > svg {
  flex-shrink: 0;
}

ui-app-sidebar .nav-item:hover {
  background: var(--surface-2);
  color: var(--text);
}

ui-app-sidebar .nav-item[aria-selected='true'] {
  background: var(--accent-dim);
  color: var(--accent);
}

ui-app-sidebar .nav-group-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-5) var(--spacing-6) var(--spacing-3);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: 1.2px;
  color: var(--text-muted);
  text-transform: uppercase;
  cursor: pointer;
  user-select: none;
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: var(--z-raised);
}

ui-app-sidebar .nav-group-header:hover {
  color: var(--text);
}

ui-app-sidebar .nav-group-chevron {
  flex-shrink: 0;
  transition: transform 0.2s;
}

ui-app-sidebar .nav-group.collapsed .nav-group-chevron {
  transform: rotate(-90deg);
}

ui-app-sidebar .nav-group-items {
  overflow: hidden;
  transition: max-height 0.2s ease;
  max-height: 1000px;
  margin-left: var(--spacing-6);
  padding-left: var(--spacing-4);
  border-left: 1px solid var(--border);
}

ui-app-sidebar .nav-group-items .nav-item {
  font-size: var(--fs-md);
  padding: var(--spacing-3) var(--spacing-5);
}

ui-app-sidebar .nav-group.collapsed .nav-group-items {
  max-height: 0;
}

ui-app-sidebar .sidebar-footer {
  margin-top: auto;
  padding: var(--spacing-6);
  border-top: 1px solid var(--border);
}

ui-theme-toggle {
  display: block;
}

/* Base theme-toggle styles — works anywhere (breadcrumb, header, standalone) */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-6);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: all 0.15s;
  font-family: inherit;
}
.theme-toggle:hover {
  border-color: var(--accent);
  color: var(--text);
}

/* Sidebar override — full width */
ui-app-sidebar .theme-toggle {
  width: 100%;
  padding: var(--spacing-4) var(--spacing-6);
}

ui-app-sidebar .sidebar-branding {
  padding: var(--spacing-6) var(--spacing-7);
  font-size: var(--fs-sm);
  color: var(--text-dim);
}

ui-app-sidebar .sidebar-branding .version {
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

/* ── Resize Handle ───────────────────────────────────────────── */

ui-app-sidebar[collapsible] {
  position: relative;
}

ui-app-sidebar .sidebar-resize-handle {
  position: absolute;
  right: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  cursor: col-resize;
  background: transparent;
  transition: background 0.15s;
  z-index: var(--z-sticky);
}

ui-app-sidebar .sidebar-resize-handle:hover,
ui-app-sidebar .sidebar-resize-handle.dragging {
  background: var(--accent);
}

/* ══════════════════════════════════════════════════════════════════
   MODULE — ui-module (breadcrumb + view container)
   ══════════════════════════════════════════════════════════════════ */

ui-module {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

ui-module .view-container {
  padding: var(--spacing-8);
  flex: 1;
  min-height: 0;
}

/* When a detail-view is shown, lock the layout chain to its container
   so sidebar and content scroll independently within their grid cells.
   Uses 100% (not 100vh) so it respects ui-platform's grid when nested. */
ui-app:has(> ui-module:first-of-type ui-view:not([style*='none']) :is(ui-detail-view, ui-policy-workspace)) {
  height: 100%;
  max-height: 100%;
  overflow: hidden;
}

ui-module:has(ui-view:not([style*='none']) :is(ui-detail-view, ui-policy-workspace)) {
  overflow: hidden;
}

/* ui-policy-workspace joins these three because it IS a record screen: it
   supplies its own edges, and 24px of module chrome around a full screen reads
   as a mistake rather than as layout. Without it the workspace sat in a gray
   gutter, its header and facts strip stopped short of both edges, and it never
   got the height lock the independent column scrolling depends on. */
ui-module:has(ui-view:not([style*='none']) :is(ui-detail-view, ui-policy-workspace)) .view-container {
  overflow: hidden;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* A wizard view (<ui-router>) supplies its own <ui-node-header>, which is
   position:sticky and carries its own inner padding, exactly like the node header
   inside a detail view. The module's default view padding boxed that header in on
   all four sides, so the new-submission wizard was the only view whose header did
   not sit flush against the top of the module. Same treatment as the detail-view
   rule above; each ui-route's own <ui-stack padding> still insets the body. Scoped
   to a router that is a DIRECT child of the view, so a router nested inside a
   detail panel is unaffected. */
ui-module:has(ui-view:not([style*='none']) > ui-router) .view-container {
  padding: 0;
}

/* An embedded document is a document: it arrives with its own page margins,
   its own type scale and its own background, and the module pane must not frame
   it a second time. Same case and same treatment as the two rules above — a
   detail view and a wizard router both supply their own edges — and it reads the
   same way on screen: 24px of app chrome around a full page looks like a mistake
   rather than like layout.

   Scoped to a frame that is a DIRECT child of the view, exactly as the router
   rule is, so a ui-embed-frame sitting inside a panel or a card keeps the
   module's normal padding. A view that wraps its frame in anything at all is
   deliberately not matched: the wrapper is then the thing deciding the inset. */
ui-module:has(ui-view:not([style*='none']) > ui-embed-frame) .view-container {
  padding: 0;
}

/* ── View & Content Panel display fixes ── */
ui-view {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ui-list-view (display:block) and ui-dashboard (no host box) do not constrain their
   own height, so a layout taller than the module is clipped by the module's
   overflow:hidden with NO scrollbar — the last rows are rendered but unreachable.
   Scoped to those two on purpose: ui-detail-view, ui-router and ui-chat-view already
   set overflow:hidden + min-height:0 on themselves, and making every ui-view a scroll
   container would silently re-anchor every position:sticky descendant to it. */
ui-view:has(> ui-list-view),
ui-view:has(> ui-dashboard) {
  overflow-y: auto;
}

/* Simple views (no detail-view): direct ui-stack children scroll */
ui-view > ui-stack {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}


/* ══════════════════════════════════════════════════════════════════
   BREADCRUMB — ui-breadcrumb
   ══════════════════════════════════════════════════════════════════ */

ui-breadcrumb {
  display: block;
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding: 0 var(--spacing-8);
}

ui-breadcrumb .breadcrumb-nav {
  display: flex;
  align-items: center;
  gap: 0;
  padding: var(--spacing-6) 0;
  font-size: var(--fs-lg);
}

ui-breadcrumb .crumb {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  color: var(--text-muted);
  text-decoration: none;
  cursor: pointer;
  font-weight: var(--fw-medium);
  transition: color 0.15s;
}

ui-breadcrumb .crumb > svg {
  flex-shrink: 0;
}

ui-breadcrumb .crumb:hover:not(.current) {
  color: var(--accent);
}

ui-breadcrumb .crumb.current {
  color: var(--text);
  font-weight: var(--fw-semibold);
  cursor: default;
}

ui-breadcrumb .crumb.crumb-home {
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

ui-breadcrumb .crumb.crumb-home svg {
  opacity: 0.8;
}

ui-breadcrumb .crumb.crumb-home:hover {
  color: var(--accent-hover);
}

ui-breadcrumb .crumb.crumb-home:hover svg {
  opacity: 1;
}

ui-breadcrumb .crumb-sep {
  margin: 0 var(--spacing-5);
  color: var(--text-dim);
  font-size: var(--fs-base);
  user-select: none;
}

/* ══════════════════════════════════════════════════════════════════
   SHARED TABLE STYLES (used by dashboard-table and list-view)
   ══════════════════════════════════════════════════════════════════ */

ui-dashboard-table table,
ui-list-view table {
  width: 100%;
  border-collapse: collapse;
}

ui-list-view table,
ui-list-view .lv-table {
  table-layout: fixed;
}

ui-dashboard-table th,
ui-list-view th {
  text-align: left;
  padding: var(--spacing-5) var(--spacing-7);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  border-bottom: 1px solid var(--border);
}

ui-dashboard-table td,
ui-list-view td {
  padding: var(--spacing-6) var(--spacing-7);
  font-size: var(--fs-lg);
  border-bottom: 1px solid var(--border);
}

ui-list-view td {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 0;
}

ui-dashboard-table tr:last-child td,
ui-list-view tr:last-child td {
  border-bottom: none;
}

ui-dashboard-table tr:hover td,
ui-list-view tbody tr:hover td {
  background: var(--surface-2);
}

ui-dashboard-table tbody tr,
ui-list-view tbody tr {
  cursor: pointer;
}

td.bold {
  font-weight: var(--fw-semibold);
}

/* ══════════════════════════════════════════════════════════════════
   BADGES (shared)
   ══════════════════════════════════════════════════════════════════ */

.badge {
  display: inline-flex;
  align-items: center;
  padding: var(--spacing-2) var(--spacing-5);
  border-radius: var(--radius-full);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
}

.badge.info {
  background: var(--info-bg);
  color: var(--info);
}
.badge.warning {
  background: var(--warning-bg);
  color: var(--warning);
}
.badge.success {
  background: var(--success-bg);
  color: var(--success);
}
.badge.danger {
  background: var(--danger-bg);
  color: var(--danger);
}
.badge.muted {
  background: rgba(107, 114, 128, 0.15);
  color: var(--muted);
}

/* ══════════════════════════════════════════════════════════════════
   BUTTONS (shared)
   ══════════════════════════════════════════════════════════════════ */

.btn {
  padding: var(--spacing-5) var(--spacing-7);
  border-radius: var(--radius);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  border: 1px solid var(--border);
  transition: all 0.15s;
  background: transparent;
  color: var(--text-muted);
}

.btn:hover {
  border-color: var(--accent);
  color: var(--text);
}

/* In-flight affordance on a shared .btn (ND-41).

   KEYED OFF `aria-busy` RATHER THAN A CLASS, deliberately. The busy state has to
   set that attribute anyway — it is what `<ui-button loading>` and
   ui-form-field's lookup button both set — so using it as the styling hook means
   the appearance and the accessible state are the same fact and cannot drift
   apart. A parallel `.btn-busy` class would be a second source of truth for one
   state.

   `progress`, NOT `not-allowed`. The work is happening; the button is not
   unavailable. This also replaces `.uib-disabled` here, which was the first
   version: that rule dims the whole button to `opacity: 0.5` and composited the
   label down to 2.83:1 on the default accent, from a token pairing that is
   4.07:1 at full opacity — dimming the one string the user is being asked to
   read. `.uib-disabled` also sets `pointer-events: none`, which made its own
   `not-allowed` cursor unreachable: the hit test returned the row underneath and
   the pointer never changed. Measured with browser-verifier.

   NO opacity, NO color, NO background here on purpose. The whole visible
   difference is the spinner appearing and the label changing, both of which read
   at full contrast on every brand.

   The two properties below are the entire CSS cost of reusing `.uib-spinner` — the
   ring `<ui-button loading>` renders — on a `.btn`, rather than adding a twelfth
   spin keyframe to the eleven ND-97 already exists to consolidate. `.btn` is a
   plain inline-block button, not `.dlg-btn`'s inline-flex row with a `gap`, so
   an inline-block spinner otherwise sits on the text baseline hard against the
   label. `.dlg-spinner` carries `vertical-align: middle` for the same reason.

   Scoped to a spinner INSIDE a `.btn`, so it changes nothing for every authored
   `.btn` that never renders one. `margin-inline-end` rather than `margin-right`
   because the spinner leads the label, and which side that is depends on the
   locale.

   The color is not set here on purpose: `.uib-spinner` is drawn in
   `currentColor`, so on `.btn-primary` it inherits var(--text-on-accent) and is
   legible on a lime accent and a navy one without this file choosing either. */
.btn[aria-busy='true'] {
  cursor: progress;
}
.btn .uib-spinner {
  vertical-align: middle;
  margin-inline-end: var(--spacing-3);
}

/* THE RULE FOR EVERY ACCENT FILL IN THIS FILE: if the background is --accent,
   the foreground is var(--text-on-accent). Never white, never var(--white).

   --accent is a carrier token and six of the eight registered brands INVERT
   --text-on-accent away from white in at least one theme — it is #151516 on
   trident's lime, #061206 on greenscreen's phosphor, #14100a on meridian,
   #140d00 on amber, #0f1117 on nukor, #071019 on oms. Hardcoding the
   foreground silently discards that: white on trident's #c6fb50 measures
   1.21:1 and on greenscreen's #52e03c 1.74:1, i.e. unreadable, which is
   exactly what was reported against `+ New Submission` (.uib-primary).
   Sixteen sites in this file did it; they are all now on the token.

   No fallback on the var(), deliberately: --text-on-accent is defined in the
   base :root scope at the top of this file AND in the light block, so an empty
   resolution is a real bug that must surface rather than be papered over by a
   hardcoded second argument. CSS-FALLBACK in tools/lint-sdk.mjs enforces that,
   and note it is line-based and does not skip comments — writing the banned
   two-argument form out in prose here is itself an error, which is how this
   comment was first drafted. */
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}

.btn-primary:hover {
  opacity: 0.9;
}

.btn-danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}

.btn-outline {
  background: transparent;
  color: var(--text-muted);
}

/* ══════════════════════════════════════════════════════════════════
   UI-BUTTON — <ui-button>
   ══════════════════════════════════════════════════════════════════ */

ui-button {
  display: inline-flex;
}

/* A primary action that owns its container — a login submit, a dialog confirm.
   Without it the only way to get a full-width button was a bespoke class. */
ui-button[full-width] {
  display: flex;
  width: 100%;
}
ui-button[full-width] .uib {
  width: 100%;
}

.uib {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--font);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: all 0.15s ease;
  background: transparent;
  color: var(--text-muted);
  white-space: nowrap;
  line-height: var(--lh-none);
  -webkit-user-select: none;
  user-select: none;
}

/* ── Sizes ── */
.uib-xs {
  padding: var(--spacing-2) var(--spacing-5);
  font-size: var(--fs-sm);
  border-radius: var(--radius-sm);
}
.uib-sm {
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
}
.uib-md {
  padding: var(--spacing-5) var(--spacing-7);
  font-size: var(--fs-md);
}
.uib-lg {
  padding: var(--spacing-6) var(--spacing-8);
  font-size: var(--fs-lg);
}

/* ── Icon-only sizing ── */
.uib-icon-only.uib-xs {
  padding: var(--spacing-2);
  width: 24px;
  height: 24px;
}
.uib-icon-only.uib-sm {
  padding: var(--spacing-3);
  width: 28px;
  height: 28px;
}
.uib-icon-only.uib-md {
  padding: var(--spacing-3);
  width: 34px;
  height: 34px;
}
.uib-icon-only.uib-lg {
  padding: var(--spacing-5);
  width: 42px;
  height: 42px;
}

/* ── Variants ── */
.uib-default:hover:not(:disabled):not([aria-disabled='true']) {
  border-color: var(--accent);
  color: var(--text);
}

.uib-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}
.uib-primary:hover:not(:disabled):not([aria-disabled='true']) {
  opacity: 0.9;
}

.uib-outline {
  background: transparent;
  border-color: var(--border);
  color: var(--text-muted);
}
.uib-outline:hover:not(:disabled):not([aria-disabled='true']) {
  border-color: var(--accent);
  color: var(--text);
}

.uib-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
}
.uib-ghost:hover:not(:disabled):not([aria-disabled='true']) {
  background: var(--surface-2);
  color: var(--text);
}

.uib-danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}
.uib-danger:hover:not(:disabled):not([aria-disabled='true']) {
  background: var(--danger-bg);
}

/* ── Disabled / Loading ── */
/* NO `pointer-events: none`, AND ITS ABSENCE IS THE POINT.
   ══════════════════════════════════════════════════════════════════
   It was here, and it silently defeated the two things this rule exists to
   provide. An element that is not a pointer target gets no hover, so:

     - `cursor: not-allowed` never applied. The cursor resolves against the
       actual hit target, which was the <ui-button> host.
     - the `tooltip` attribute never appeared. ui-button renders it as the
       <button>'s native `title`, and a UA tooltip is driven by hover.

   MEASURED, not reasoned: elementFromPoint at the dead center of a disabled
   button returned `ui-button`, not the <button>, and `btn.matches(':hover')`
   was false with the pointer on it.

   That is invariant 0 in its worst shape. `<ui-button disabled tooltip="...">`
   is the SDK's only way to say "you cannot do this, and here is why" — a
   consumer's account screen ships five verbs that way — and the reason was in
   the DOM, in the accessibility tree, and unreadable by any user. The button
   looked disabled and looked like it carried an explanation.

   It also prevented nothing. ui-button sets the native `disabled` property on
   the rendered <button> and guards its own click handler, so activation was
   already impossible twice over. Re-verified after the change with a real CDP
   press/release: no `click`, no `mousedown`, no `button-click`, no focus. The
   hover rules above are scoped `:not(:disabled)` so a disabled button does not
   brighten under the pointer now that the pointer reaches it — measured as
   opacity 0.5 held through hover, against an enabled sibling that does go
   1 -> 0.9, so the two cases are distinguishable and hover is not simply dead.

   ONE BEHAVIOR THIS DOES CHANGE, stated because it is not obvious. `pointerdown`
   and `pointerup` now fire on a disabled button; `click` and `mousedown` are still
   suppressed by the native `disabled` attribute. Chrome does not bubble a click
   from a disabled control, so a disabled ui-button nested inside a CLICKABLE
   ancestor would now swallow that ancestor's click instead of letting the pointer
   fall through to it. Measured before shipping: the authored corpus contains four
   `<ui-button disabled>` instances, all four in one file's action card, and none
   of them has a clickable ancestor — so there are zero affected sites today. If a
   clickable row ever needs a disabled button inside it, that is the case to
   revisit, and it needs a real answer rather than restoring `pointer-events: none`
   and making every reason unreadable again.

   THE VARIANT HOVER RULES ABOVE EXCLUDE `[aria-disabled='true']` AS WELL AS
   `:disabled`, and the second half is not redundant (ND-84). A button held by
   `disabled-until-valid` is aria-disabled WITHOUT the native property — that is
   what keeps its click, and therefore its explanation, reachable at all — so
   `:not(:disabled)` alone let it brighten under the pointer and read as
   available. Both mechanisms are named rather than collapsing to the aria one,
   which ui-button happens to set in both cases today: `:disabled` is what the
   platform enforces and it should not depend on an attribute staying in sync.
   This rule needs no such change, because ui-button adds the class for all
   three states. */
.uib-disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ── Focus ring ── */
.uib:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Icon slot ── */
.uib-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.uib-label {
  display: inline-block;
}

/* ── Inline spinner ── */
.uib-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-circle);
  animation: uib-spin 0.6s linear infinite;
  opacity: 0.7;
}
@keyframes uib-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ══════════════════════════════════════════════════════════════════
   UI-BADGE — <ui-badge>
   ══════════════════════════════════════════════════════════════════ */

ui-badge {
  display: inline-block;
}

.bdg {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-5);
  border-radius: var(--radius-full);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  font-family: var(--font);
  line-height: var(--lh-tight);
  white-space: nowrap;
}
.bdg-sm {
  padding: var(--spacing-1) var(--spacing-4);
  font-size: var(--fs-xs);
}

.bdg-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}

/* ── Variants ── */
.bdg-default {
  background: var(--surface-2);
  color: var(--text-muted);
}
.bdg-success {
  background: var(--success-bg);
  color: var(--success);
}
.bdg-warning {
  background: var(--warning-bg);
  color: var(--warning);
}
.bdg-danger {
  background: var(--danger-bg);
  color: var(--danger);
}
.bdg-info {
  background: var(--info-bg);
  color: var(--info);
}
.bdg-secondary {
  background: rgba(107, 114, 128, 0.15);
  color: var(--muted);
}
.bdg-muted {
  background: var(--surface-2);
  color: var(--text-dim);
}

/* ══════════════════════════════════════════════════════════════════
   UI-SPINNER — <ui-spinner>
   ══════════════════════════════════════════════════════════════════ */

ui-spinner {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.spn {
  display: inline-flex;
}
.spn-circle {
  display: block;
  border-radius: var(--radius-circle);
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  animation: spn-spin 0.6s linear infinite;
}
@keyframes spn-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ── Sizes ── */
.spn-xs .spn-circle {
  width: 12px;
  height: 12px;
  border-width: 1.5px;
}
.spn-sm .spn-circle {
  width: 16px;
  height: 16px;
  border-width: 2px;
}
.spn-md .spn-circle {
  width: 24px;
  height: 24px;
  border-width: 2px;
}
.spn-lg .spn-circle {
  width: 36px;
  height: 36px;
  border-width: 3px;
}

/* ══════════════════════════════════════════════════════════════════
   UI-ICON — <ui-icon>
   ══════════════════════════════════════════════════════════════════ */

ui-icon {
  display: inline-flex;
  align-items: center;
}
.ico {
  display: inline-flex;
  align-items: center;
  line-height: var(--lh-flush);
}

/* ══════════════════════════════════════════════════════════════════
   UI-STACK — <ui-stack>
   ══════════════════════════════════════════════════════════════════ */

ui-stack {
  display: flex;
  flex-direction: column;
}
ui-stack[inline] {
  display: inline-flex;
}

/* ── Direction ── */
ui-stack[direction='row'] {
  flex-direction: row;
  align-items: center;
}
ui-stack[direction='column'] {
  flex-direction: column;
}

/* ── Gap ── */
ui-stack[gap='none'] {
  gap: 0;
}
ui-stack[gap='xs'] {
  gap: var(--spacing-2);
}
ui-stack[gap='sm'] {
  gap: var(--spacing-4);
}
ui-stack,
ui-stack[gap='md'] {
  gap: var(--spacing-7);
}
ui-stack[gap='lg'] {
  gap: var(--spacing-8);
}
ui-stack[gap='xl'] {
  gap: var(--spacing-9);
}

/* ── Padding ── */
ui-stack[padding='none'] {
  padding: 0;
}
ui-stack[padding='xs'] {
  padding: var(--spacing-2);
}
ui-stack[padding='sm'] {
  padding: var(--spacing-4);
}
ui-stack[padding='md'] {
  padding: var(--spacing-7);
}
ui-stack[padding='lg'] {
  padding: var(--spacing-8);
}
ui-stack[padding='xl'] {
  padding: var(--spacing-9);
}

/* ── Align ── */
ui-stack[align='start'] {
  align-items: flex-start;
}
ui-stack[align='center'] {
  align-items: center;
}
ui-stack[align='end'] {
  align-items: flex-end;
}
ui-stack[align='stretch'] {
  align-items: stretch;
}

/* ── Justify ── */
ui-stack[justify='start'] {
  justify-content: flex-start;
}
ui-stack[justify='center'] {
  justify-content: center;
}
ui-stack[justify='end'] {
  justify-content: flex-end;
}
ui-stack[justify='between'] {
  justify-content: space-between;
}
ui-stack[justify='around'] {
  justify-content: space-around;
}

/* ── Wrap (default on for row) ── */
ui-stack[wrap] {
  flex-wrap: wrap;
}
ui-stack[direction='row'] {
  flex-wrap: wrap;
}
ui-stack[wrap='false'] {
  flex-wrap: nowrap;
}

/* ── Semantic surface variants ── */
ui-stack[surface] {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--spacing-7) var(--spacing-7);
}

/* Fill the viewport. With align/justify this centers content on a page that has
   no surrounding layout — an unauthenticated page. `min-height` rather than
   `height` so a tall card scrolls instead of being clipped. */
ui-stack[full-height] {
  min-height: 100vh;
  box-sizing: border-box;
}

/* max-width itself is set as an inline style by _applyMaxWidth(), because it
   takes an arbitrary length rather than a token. This only makes the constraint
   effective inside a flex parent, where the default is to stretch. */
ui-stack[max-width] {
  width: 100%;
  box-sizing: border-box;
}

/* ══════════════════════════════════════════════════════════════════
   DASHBOARD — ui-dashboard-hero, ui-dashboard-grid, ui-metric-card, ui-dashboard-table
   ══════════════════════════════════════════════════════════════════ */

/* Dashboard Hero */
ui-dashboard-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-2) 0 var(--spacing-7);
  gap: var(--spacing-4) var(--spacing-7);
}

ui-dashboard-hero .dh-title-section {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

ui-dashboard-hero .dh-title {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  color: var(--text);
  margin: 0;
  line-height: var(--lh-tight);
}

ui-dashboard-hero .dh-subtitle {
  font-size: var(--fs-md);
  color: var(--text-muted);
  margin: 0;
}

ui-dashboard-hero .dh-actions {
  display: flex;
  gap: var(--spacing-5);
  align-items: center;
  flex-shrink: 0;
}

ui-dashboard-hero ui-dashboard-toolbar {
  flex-basis: 100%;
  padding: 0;
}

ui-dashboard-grid {
  display: grid;
  gap: 1rem;
  margin-bottom: var(--spacing-8);
}

ui-dashboard-grid[span='full'],
ui-dashboard-grid [span='full'] {
  grid-column: 1 / -1;
}

/* Metric Grid — Responsive container for metric cards, charts, and graphs */
ui-metric-grid {
  display: grid;
  container-type: inline-size;
  gap: 1rem;
  grid-template-columns: repeat(var(--mg-cols, 4), 1fr);
  align-items: stretch;
  min-width: 0;
}

ui-metric-grid[span='full'],
ui-metric-grid [span='full'] {
  grid-column: 1 / -1;
}

@container (max-width: 900px) {
  ui-metric-grid {
    grid-template-columns: repeat(var(--mg-cols-tablet, 2), 1fr);
  }
}

@container (max-width: 500px) {
  ui-metric-grid {
    grid-template-columns: repeat(var(--mg-cols-mobile, 1), 1fr);
  }
}

/* Metric Card — Base (default: flat layout) */
ui-metric-card {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--spacing-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--spacing-6) var(--spacing-7);
  transition: border-color 0.15s, background 0.15s;
  min-width: 0;
  flex: 1;
}

ui-metric-card:hover {
  border-color: color-mix(in srgb, var(--text-muted) 30%, transparent);
  background: color-mix(in srgb, var(--surface) 95%, white);
}

ui-metric-card .metric-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--spacing-1);
  min-width: 0;
  text-align: right;
}

ui-metric-card .metric-icon {
  width: 32px;
  height: 32px;
  border-radius: var(--radius);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
ui-metric-card .metric-icon svg {
  width: 16px;
  height: 16px;
}

ui-metric-card .metric-value {
  font-size: var(--fs-4xl);
  font-weight: var(--fw-extrabold);
  line-height: var(--lh-none);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
  color: var(--text);
}

ui-metric-card .metric-label {
  font-size: var(--fs-base);
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Metric Card — Trend indicator */
ui-metric-card .metric-trend {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  margin-left: auto;
  flex-shrink: 0;
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--text-muted) 8%, transparent);
}
ui-metric-card .metric-trend--up {
  color: var(--success);
  background: color-mix(in srgb, var(--success) 10%, transparent);
}
ui-metric-card .metric-trend--down {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 10%, transparent);
}
ui-metric-card .metric-trend--flat { color: var(--text-muted); }

/* Metric Card — Size: sm */
ui-metric-card[size='sm'] {
  padding: var(--spacing-4) var(--spacing-6);
  gap: var(--spacing-4);
  border-radius: var(--radius);
}
ui-metric-card[size='sm'] .metric-icon {
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
}
ui-metric-card[size='sm'] .metric-icon svg {
  width: 13px;
  height: 13px;
}
ui-metric-card[size='sm'] .metric-value {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
}
ui-metric-card[size='sm'] .metric-label {
  font-size: var(--fs-xs);
}
ui-metric-card[size='sm'] .metric-trend {
  font-size: var(--fs-xs);
  padding: var(--spacing-1) var(--spacing-2);
}

/* Metric Card — Size: lg */
ui-metric-card[size='lg'] {
  padding: var(--spacing-7) var(--spacing-8);
  gap: var(--spacing-7);
  border-radius: var(--radius-lg);
}
ui-metric-card[size='lg'] .metric-icon {
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
}
ui-metric-card[size='lg'] .metric-icon svg {
  width: 22px;
  height: 22px;
}
ui-metric-card[size='lg'] .metric-value {
  font-size: var(--fs-5xl);
}
ui-metric-card[size='lg'] .metric-label {
  font-size: var(--fs-md);
}
ui-metric-card[size='lg'] .metric-trend {
  font-size: var(--fs-base);
}

/* Metric Card — Layout: vertical (icon top, body below) */
ui-metric-card[layout='vertical'] {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-6);
  padding: var(--spacing-7) var(--spacing-7);
  border-radius: var(--radius-lg);
}
ui-metric-card[layout='vertical'] .metric-icon {
  width: 36px;
  height: 36px;
  border-radius: var(--radius);
}
ui-metric-card[layout='vertical'] .metric-icon svg {
  width: 18px;
  height: 18px;
}
ui-metric-card[layout='vertical'] .metric-body {
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-1);
}
ui-metric-card[layout='vertical'] .metric-value {
  font-size: var(--fs-5xl);
  font-weight: var(--fw-bold);
}
ui-metric-card[layout='vertical'] .metric-label {
  font-size: var(--fs-base);
}
ui-metric-card[layout='vertical'] .metric-trend {
  margin-left: 0;
  margin-top: var(--spacing-2);
}

/* Metric Card — Layout: horizontal (icon left, body right) */
ui-metric-card[layout='horizontal'] {
  flex-direction: row;
  align-items: center;
  gap: var(--spacing-6);
}
ui-metric-card[layout='horizontal'] .metric-body {
  flex: 1;
  min-width: 0;
}

/* Metric Card — Layout: compact (icon + value inline, label below) */
ui-metric-card[layout='compact'] {
  flex-direction: column;
  gap: var(--spacing-3);
}
ui-metric-card[layout='compact'] .metric-icon {
  position: absolute;
  top: 12px;
  right: 12px;
}
ui-metric-card[layout='compact'] {
  position: relative;
}

/* Metric Card — Layout: inline (no border, pure inline stat — minimal height) */
ui-metric-card[layout='inline'] {
  flex-direction: row;
  align-items: center;
  gap: var(--spacing-4);
  background: none;
  border: none;
  border-radius: 0;
  padding: var(--spacing-3) 0;
}
ui-metric-card[layout='inline'] .metric-icon {
  width: 22px;
  height: 22px;
  border-radius: 5px;
}
ui-metric-card[layout='inline'] .metric-icon svg {
  width: 12px;
  height: 12px;
}
ui-metric-card[layout='inline'] .metric-body {
  flex-direction: row;
  align-items: baseline;
  gap: var(--spacing-3);
}
ui-metric-card[layout='inline'] .metric-value {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
}
ui-metric-card[layout='inline'] .metric-label {
  font-size: var(--fs-sm);
}
ui-metric-card[layout='inline'] .metric-trend {
  padding: var(--spacing-1) var(--spacing-2);
  font-size: var(--fs-xs);
}
ui-metric-card[layout='inline']:hover {
  border-color: transparent;
  background: none;
}

/* Metric Card — Layout: flat (same as base default — explicit attr is a no-op) */

/* Metric Card — Icon Size overrides */
ui-metric-card[icon-size='sm'] .metric-icon {
  width: 24px;
  height: 24px;
  font-size: var(--fs-base);
  border-radius: var(--radius-sm);
}
ui-metric-card[icon-size='lg'] .metric-icon {
  width: 48px;
  height: 48px;
  font-size: var(--fs-4xl);
  border-radius: var(--radius-md);
}

/* Color variants for metric icon/value */
ui-metric-card[color='info'] .metric-icon {
  background: var(--info-bg);
  color: var(--info);
}
ui-metric-card[color='warning'] .metric-icon {
  background: var(--warning-bg);
  color: var(--warning);
}
ui-metric-card[color='success'] .metric-icon {
  background: var(--success-bg);
  color: var(--success);
}
ui-metric-card[color='danger'] .metric-icon {
  background: var(--danger-bg);
  color: var(--danger);
}
ui-metric-card:not([color]) .metric-icon {
  background: var(--accent-dim);
  color: var(--accent);
}

ui-metric-card:not([color]) .metric-value {
  color: var(--text);
}

/* Dashboard Table */
ui-dashboard-table {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

ui-dashboard-table .table-header {
  padding: var(--spacing-7) var(--spacing-7);
  font-size: 15px;
  font-weight: var(--fw-semibold);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

ui-dashboard-table .empty {
  padding: var(--spacing-8) var(--spacing-7);
  text-align: center;
  color: var(--text-dim);
  font-size: var(--fs-md);
}

ui-dashboard-table .table-view-all {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--accent);
  text-decoration: none;
  transition: color 0.15s;
}

ui-dashboard-table .table-view-all:hover {
  color: var(--accent-hover);
  text-decoration: underline;
}

ui-dashboard-table .truncate {
  display: inline-block;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}


/* ══════════════════════════════════════════════════════════════════
   LIST VIEW — ui-list-view
   ══════════════════════════════════════════════════════════════════ */

ui-list-view {
  display: block;
}

/* ── Param Fields ── */
ui-list-view .lv-param-fields {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  margin-bottom: var(--spacing-4);
}

/* ── Toolbar ── */
ui-list-view .lv-toolbar {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  margin-bottom: var(--spacing-7);
  flex-wrap: wrap;
}

ui-list-view .lv-search-box {
  flex: 1;
  min-width: 200px;
  display: flex;
}

ui-list-view .lv-search-input {
  flex: 1;
  padding: var(--spacing-5) var(--spacing-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius) 0 0 var(--radius);
  color: var(--text);
  font-size: var(--fs-lg);
  outline: none;
}

ui-list-view .lv-search-input:focus {
  border-color: var(--accent);
}

ui-list-view .lv-search-field {
  padding: var(--spacing-5) var(--spacing-5);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: none;
  border-radius: 0 var(--radius) var(--radius) 0;
  color: var(--text-muted);
  font-size: var(--fs-md);
  outline: none;
}

/* ── Focus ring ── */
/* Native <select> (ui-list-view.js:206) with the outline stripped. It sits in a
   joined control group with the search input, so the ring is inset to avoid
   overlapping the neighbor's border. (ND-35) */
ui-list-view .lv-search-field:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ── Status Chips ── */
ui-list-view .lv-status-chips-wrap {
  position: relative;
  flex-basis: 100%;
  min-width: 0;
}

ui-list-view .lv-status-chips-wrap::before,
ui-list-view .lv-status-chips-wrap::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 32px;
  pointer-events: none;
  z-index: var(--z-elevated);
  opacity: 0;
  transition: opacity 0.2s;
}

ui-list-view .lv-status-chips-wrap::before {
  left: 0;
  background: linear-gradient(to right, var(--bg), transparent);
}

ui-list-view .lv-status-chips-wrap::after {
  right: 0;
  background: linear-gradient(to left, var(--bg), transparent);
}

ui-list-view .lv-status-chips-wrap.lv-fade-left::before {
  opacity: 1;
}

ui-list-view .lv-status-chips-wrap.lv-fade-right::after {
  opacity: 1;
}

ui-list-view .lv-status-chips {
  display: flex;
  gap: var(--spacing-3);
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  scroll-behavior: smooth;
}

ui-list-view .lv-status-chips::-webkit-scrollbar {
  display: none;
}

ui-list-view .lv-chip {
  padding: var(--spacing-3) var(--spacing-6);
  border-radius: var(--radius-full);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  transition: all 0.15s;
  user-select: none;
  white-space: nowrap;
  flex-shrink: 0;
}

ui-list-view .lv-chip:hover {
  border-color: var(--accent);
  color: var(--text);
}

ui-list-view .lv-chip[aria-selected='true'] {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--accent);
}

/* A chip is role="option" in a role="listbox" and carries tabindex="0"
   (ui-list-view.js, interactive('.lv-chip')), so it is a keyboard control and
   needs a ring. Inset like the search field's, because a chip is pill-shaped and
   an outset ring on a radius-full border reads as a second border. (ND-98) */
ui-list-view .lv-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

ui-list-view .lv-chip-count {
  font-size: var(--fs-sm);
  opacity: 0.7;
  margin-left: var(--spacing-2);
}

/* ── List Toolbar (ui-list-toolbar) ── */
ui-list-toolbar {
  display: block;
  margin-bottom: var(--spacing-7);
}

ui-dashboard-grid > ui-list-toolbar {
  margin-bottom: 0;
}

/* Inside ui-list-view the pills sit right of a narrow column; at dashboard-grid
   width that strands them past ~760px of gap. */
ui-dashboard-grid > ui-list-toolbar .lt-filters {
  justify-content: flex-start;
}

ui-list-toolbar .lt-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--spacing-3) var(--spacing-5);
}

ui-list-toolbar .lt-search {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  flex: 0 1 280px;
  min-width: 120px;
  border-right: 1px solid var(--border);
  padding-right: var(--spacing-5);
}

ui-list-toolbar .lt-search ui-icon {
  color: var(--text-muted);
  flex-shrink: 0;
}

ui-list-toolbar .lt-search-input {
  flex: 1;
  background: none;
  border: none;
  color: var(--text);
  font-size: var(--fs-md);
  outline: none;
  padding: var(--spacing-2) 0;
  min-width: 0;
}

/* ── Focus ring ── */
/* Borderless input inside a bordered toolbar; outline was stripped with no
   replacement. Inset offset to stay within the toolbar chrome. (ND-35) */
ui-list-toolbar .lt-search-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

ui-list-toolbar .lt-search-input::placeholder {
  color: var(--text-muted);
}

ui-list-toolbar .lt-search-field {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: var(--fs-base);
  outline: none;
  cursor: pointer;
  padding: var(--spacing-2) var(--spacing-1);
}

/* ── Focus ring ── */
/* This is a native <select> (ui-list-toolbar.js:139) with its outline removed
   and nothing in its place, so keyboard users had no indication it was the
   active control. (ND-35) */
ui-list-toolbar .lt-search-field:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

ui-list-toolbar .lt-filters {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-3);
  flex: 1;
  min-width: 0;
  overflow-x: auto;
}

ui-list-toolbar .lt-filters::-webkit-scrollbar {
  display: none;
}

ui-list-toolbar .lt-facet-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--fs-md);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s, background 0.15s;
  line-height: var(--lh-normal);
}

ui-list-toolbar .lt-facet-pill:hover {
  border-color: var(--accent);
}

ui-list-toolbar .lt-facet-pill[aria-expanded="true"] {
  border-color: var(--accent);
  background: var(--surface-3);
}

ui-list-toolbar .lt-facet-label {
  color: var(--text-muted);
  font-size: var(--fs-base);
}

ui-list-toolbar .lt-facet-value {
  font-weight: var(--fw-medium);
}

ui-list-toolbar .lt-facet-pill ui-icon {
  color: var(--text-muted);
  transition: transform 0.15s;
}

ui-list-toolbar .lt-facet-pill[aria-expanded="true"] ui-icon {
  transform: rotate(180deg);
}

ui-list-toolbar .lt-add-filter {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-4);
  background: none;
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--fs-md);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s, color 0.15s;
}

ui-list-toolbar .lt-add-filter:hover {
  border-color: var(--accent);
  color: var(--text);
}

ui-list-toolbar .lt-reset {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-4);
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: var(--fs-base);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s;
}

ui-list-toolbar .lt-reset:hover {
  color: var(--danger);
}

/* Dropdown (portaled to body) */
.lt-dropdown {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--spacing-2) 0;
  z-index: var(--z-dropdown);
  box-shadow: var(--elev-dropdown-sm);
  max-height: 240px;
  overflow-y: auto;
  font-family: var(--font);
}

.lt-dropdown-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-5);
  color: var(--text);
  font-size: var(--fs-md);
  cursor: pointer;
  transition: background 0.1s;
}

.lt-dropdown-item:hover {
  background: var(--surface-3);
}

.lt-dropdown-item.lt-selected {
  color: var(--accent);
  font-weight: var(--fw-medium);
}

.lt-dropdown-count {
  color: var(--text-muted);
  font-size: var(--fs-base);
  margin-left: auto;
}

.lt-check {
  font-size: var(--fs-lg);
  line-height: 1;
  color: var(--text-muted);
}

.lt-dropdown-item.lt-selected .lt-check {
  color: var(--accent);
}

ui-list-toolbar .lt-facet-pill.lt-facet-active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

ui-list-toolbar .lt-facet-pill.lt-facet-active .lt-facet-value {
  color: var(--accent);
}

/* ── Dynamic List Filters (ui-list-filters) ── */
ui-list-filters {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
  flex-basis: 100%;
}

ui-list-filters .lf-dimension {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}

ui-list-filters .lf-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  white-space: nowrap;
  min-width: fit-content;
}

ui-list-filters .lf-chips-wrap {
  position: relative;
  min-width: 0;
  flex: 1;
}

ui-list-filters .lf-chips {
  display: flex;
  gap: var(--spacing-3);
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  scroll-behavior: smooth;
}

ui-list-filters .lf-chips::-webkit-scrollbar {
  display: none;
}

ui-list-filters .lf-chip {
  padding: var(--spacing-3) var(--spacing-6);
  border-radius: var(--radius-full);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
  cursor: pointer;
  transition: all 0.15s;
  white-space: nowrap;
  flex-shrink: 0;
}

ui-list-filters .lf-chip:hover {
  border-color: var(--accent);
  color: var(--text);
}

ui-list-filters .lf-chip[aria-selected='true'] {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--accent);
}

ui-list-filters .lf-chip-count {
  font-size: var(--fs-sm);
  opacity: 0.7;
  margin-left: var(--spacing-2);
}

/* ── Table Card ── */
ui-list-view .lv-table-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  position: relative;
}

/* ── Sort Headers ── */
ui-list-view .lv-th-sortable {
  cursor: pointer;
  user-select: none;
  transition: color 0.15s;
}

ui-list-view .lv-th-sortable:hover {
  color: var(--text);
}

ui-list-view .lv-th-sortable:hover .lv-sort-icon {
  color: var(--accent);
}

/* Sorting is now reachable by keyboard (interactive('.lv-th-sortable')), so the
   header is a focus target. Inset, because a <th> sits flush against the table
   border and an outset ring would be clipped by the card's overflow:hidden.
   (ND-98) */
ui-list-view .lv-th-sortable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

ui-list-view .lv-sort-icon {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  transition: color 0.15s;
}

ui-list-view .lv-sort-icon.lv-sort-active {
  color: var(--accent);
}

/* ── Rows ── */
ui-list-view .lv-row {
  cursor: pointer;
  transition: background 0.1s;
}

ui-list-view .lv-cell-bold {
  font-weight: var(--fw-semibold);
}

/* Cell renderers ui-column declares. Without these the markup would be emitted
   against undefined classes and render identically to plain text — a renderer
   that appears implemented but is not. */
ui-list-view .lv-mono {
  font-family: var(--mono);
  font-size: var(--fs-sm);
}

ui-list-view .lv-muted {
  color: var(--text-muted);
}

ui-list-view .lv-truncate {
  display: inline-block;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}

/* ── Empty + Loading ── */
ui-list-view .lv-empty {
  padding: var(--spacing-10) var(--spacing-8);
  text-align: center;
  color: var(--text-dim);
  font-size: var(--fs-lg);
}

/* A failed load is not an empty list, and must not be dimmed like one. */
ui-list-view .lv-empty.lv-empty-error {
  color: var(--color-danger-fg, var(--text-primary));
}

ui-list-view .lv-loading {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.2);
  z-index: var(--z-loading);
}

ui-list-view .lv-loading[hidden] {
  display: none;
}

ui-list-view .lv-spinner {
  width: 24px;
  height: 24px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: var(--radius-circle);
  animation: lv-spin 0.6s linear infinite;
}

@keyframes lv-spin {
  to { transform: rotate(360deg); }
}

/* ── Pagination ── */
ui-list-view .lv-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-6) 0;
  margin-top: var(--spacing-6);
}

ui-list-view .lv-page-info {
  font-size: var(--fs-md);
  color: var(--text-muted);
}

ui-list-view .lv-page-controls {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}

ui-list-view .lv-page-btn {
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
  transition: all 0.15s;
}

ui-list-view .lv-page-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
}

ui-list-view .lv-page-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

ui-list-view .lv-page-numbers {
  display: flex;
  align-items: center;
  gap: var(--spacing-1);
}

ui-list-view .lv-page-num {
  min-width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-md);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: all 0.15s;
}

ui-list-view .lv-page-num:hover {
  background: var(--surface-2);
  color: var(--text);
}

ui-list-view .lv-page-num.lv-page-active {
  background: var(--accent-dim);
  border-color: var(--accent);
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

ui-list-view .lv-page-ellipsis {
  padding: 0 var(--spacing-3);
  color: var(--text-dim);
  font-size: var(--fs-lg);
}


/* ══════════════════════════════════════════════════════════════════
   DIALOG (shared overlay)
   ══════════════════════════════════════════════════════════════════ */

.overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: var(--z-menu);
  align-items: center;
  justify-content: center;
}

.overlay.open {
  display: flex;
}

.dialog {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--spacing-8);
  width: 420px;
  max-width: 90vw;
}

.dialog h3 {
  margin-bottom: var(--spacing-7);
  font-size: var(--fs-2xl);
}

.form-field {
  margin-bottom: var(--spacing-7);
}

.form-field label {
  display: block;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  margin-bottom: var(--spacing-3);
  text-transform: uppercase;
  letter-spacing: 0.3px;
}

/* `input` unqualified rather than input[type="text"], so ui-list-new-item's
   date and number controls are styled by the same rule as its text box — a
   native control left unstyled beside three styled siblings does not read as a
   different input type, it reads as a broken one. ND-1086. */
.form-field input,
.form-field textarea,
.form-field select {
  width: 100%;
  padding: var(--spacing-5) var(--spacing-6);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  /* NOT inherited by a form control — the UA supplies its own, and it differs by
     control: MEASURED in Chrome on this dialog, the text and number inputs came
     out "Arial" and the date input "monospace" while the labels around them were
     Inter. So a date box read as a different KIND of control rather than as a
     date one. .ff-input has always carried this line; this rule was written
     without it and the omission was invisible until ND-1086 rendered a control
     the UA styles differently. */
  font-family: inherit;
  font-size: var(--fs-lg);
  outline: none;
  box-sizing: border-box;
}

.form-field textarea {
  min-height: 80px;
  resize: vertical;
  line-height: var(--lh-relaxed);
}

.form-field input:focus,
.form-field textarea:focus,
.form-field select:focus {
  border-color: var(--accent);
}

/* Custom SDK Select Dropdown */
.sdk-select {
  position: relative;
}

.sdk-select-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: var(--spacing-5) var(--spacing-6);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: var(--fs-lg);
  cursor: pointer;
  box-sizing: border-box;
  transition: border-color 0.15s;
}

.sdk-select-trigger:hover {
  border-color: var(--text-muted);
}

.sdk-select-has-value {
  color: var(--text);
}

.sdk-select-arrow {
  font-size: var(--fs-base);
  color: var(--text-muted);
}

.sdk-select-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: var(--spacing-2);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--elev-dropdown);
  max-height: 240px;
  overflow-y: auto;
  z-index: var(--z-menu);
}

.sdk-select-dropdown.open {
  display: block;
}

.sdk-select-option {
  padding: var(--spacing-4) var(--spacing-6);
  color: var(--text);
  font-size: var(--fs-lg);
  cursor: pointer;
  transition: background 0.1s;
}

.sdk-select-option:hover {
  background: var(--accent);
  color: var(--text-on-accent);
}

.sdk-select-option:first-child {
  border-radius: var(--radius) var(--radius) 0 0;
}

.sdk-select-option:last-child {
  border-radius: 0 0 var(--radius) var(--radius);
}

.form-actions {
  display: flex;
  gap: var(--spacing-4);
  justify-content: flex-end;
  margin-top: var(--spacing-8);
}

.required {
  color: var(--danger);
}

/* ══════════════════════════════════════════════════════════════════
   DIALOG — ui-dialog (v2 declarative modal)
   ══════════════════════════════════════════════════════════════════ */

ui-dialog {
  display: contents;
}

/* ─── Backdrop ─── */
.dlg-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: var(--z-modal);
  align-items: center;
  justify-content: center;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  animation: dlg-fade-in 0.15s ease-out;
}
.dlg-backdrop.dlg-open {
  display: flex;
}

@keyframes dlg-fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

/* ─── Panel ─── */
.dlg-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  max-height: 90vh;
  box-shadow:
    var(--elev-modal);
  animation: dlg-slide-up 0.2s ease-out;
  /* Inert, deliberately left in place: .dlg-panel carries no tabindex
     (ui-dialog.js:241) so it can never receive focus, and an unfocusable
     element cannot render a focus ring. Do NOT "fix" this by adding a
     :focus-visible ring — it would never match. The real gap is that
     _focusFirst() (ui-dialog.js:462) silently does nothing when a dialog has
     no focusable children, leaving focus outside the modal. Resolved by
     ND-81's migration to native <dialog>, where the UA manages this. (ND-35) */
  outline: none;
}

@keyframes dlg-slide-up {
  from {
    transform: translateY(12px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}

/* ─── Size Variants ─── */
.dlg-sm {
  width: 400px;
  max-width: 92vw;
}
.dlg-md {
  width: 560px;
  max-width: 92vw;
}
.dlg-lg {
  width: 780px;
  max-width: 92vw;
}
.dlg-full {
  width: 96vw;
  height: 92vh;
  max-width: 96vw;
  max-height: 92vh;
}

/* ─── Header ─── */
.dlg-header {
  display: flex;
  align-items: center;
  padding: var(--spacing-7) var(--spacing-8) 0;
  gap: var(--spacing-6);
}
.dlg-header-danger .dlg-title {
  color: var(--danger);
}
/*
 * A QUALIFIED SUCCESS IS NOT A FAILURE, AND MUST NOT BE PAINTED AS ONE.
 *
 * ui-actions settled its completion dialog as `danger` whenever the result carried any
 * warning, so issuing a policy that succeeded — premium booked, 58-page packet rendered —
 * showed "Issuing policy — complete" in the same red as "Issuing policy — failed", over the
 * non-fatal note that e-sign had been skipped. Observed on POL-2026-00030; read as a failure
 * by the person who issued it. Two outcomes that need different actions must not share a
 * color.
 */
.dlg-header-warning .dlg-title {
  color: var(--warning);
}
.dlg-title {
  font-size: var(--fs-title);
  font-weight: var(--fw-bold);
  color: var(--text);
  line-height: var(--lh-tight);
}
.dlg-close-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition:
    background 0.12s,
    color 0.12s;
  flex-shrink: 0;
}
.dlg-close-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

/* ─── Description ─── */
/* Row-scoped context interpolated from open({ prefill }) — see
   ui-dialog's description-template. Deliberately quiet: it is the context a
   title bar was being overloaded with ("Change Reserve — col / Indemnity
   (outstanding $8,000.00)"), so it must read as a caption under the title and
   not compete with it.

   Horizontal padding matches .dlg-header and .dlg-body so the three align; the
   top margin is negative-free because .dlg-header's bottom padding is 0.

   :empty collapses it, so a dialog opened with no prefill has no dead gap —
   and no JS writes display, which would make visibility state (invariant 7). */
.dlg-description {
  padding: var(--spacing-3) var(--spacing-8) 0;
  font-size: var(--fs-md);
  color: var(--text-muted);
  line-height: var(--lh-normal);
  /* Row data is where unbroken 40-character ids, emails and URLs come from, and
     without this a single one of them paints straight out of the panel and
     across the page (measured: scrollWidth 2597 in a 558px panel). .dlg-title
     has the same defect and is NOT fixed here — it is pre-existing and a title
     is authored text, where this line is a value someone else owns. */
  overflow-wrap: anywhere;
}
.dlg-description:empty {
  display: none;
}
/* Grid for labeled key-value pairs inside description-template. Two columns:
   a muted label and a normal-weight value, matching ui-key-value's visual
   language so the description reads like the rest of the app's read-only
   context rather than a paragraph of text. */
.dlg-desc-grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--spacing-1) var(--spacing-4);
}
.dlg-desc-label {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}
.dlg-desc-value {
  font-size: var(--fs-sm);
  color: var(--text);
  font-weight: var(--fw-normal);
  overflow-wrap: anywhere;
}
.dlg-desc-span {
  grid-column: 1 / -1;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ─── Body ─── */
.dlg-body {
  padding: var(--spacing-7) var(--spacing-8);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  color: var(--text);
  font-size: var(--fs-lg);
  line-height: 1.55;
}
.dlg-body > ui-form {
  padding-top: var(--spacing-2);
}

/* ─── Error Banner ─── */
/* Progress in a streaming dialog. Shares the error band's box so the dialog does not
   jump when one replaces the other, and none of its color — a red alert beside
   "Generating…" reads as a failure that has not happened. */
.dlg-error.dlg-status {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text-secondary);
}

.dlg-error.dlg-status .dlg-spinner {
  border-color: var(--border);
  border-top-color: var(--accent);
}

.dlg-error {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-5) var(--spacing-8);
  background: var(--danger-bg);
  color: var(--danger);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  border-top: 1px solid color-mix(in srgb, var(--danger) 25%, transparent);
}
.dlg-error svg {
  flex-shrink: 0;
}

/* ─── Footer ─── */
.dlg-footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-4);
  padding: var(--spacing-7) var(--spacing-8);
  border-top: 1px solid var(--border);
}

/* ─── Buttons ─── */
.dlg-btn {
  padding: var(--spacing-4) var(--spacing-7);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  font-family: inherit;
  border-radius: var(--radius-sm);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  border: 1px solid transparent;
  transition:
    background 0.12s,
    color 0.12s,
    border-color 0.12s,
    opacity 0.12s;
  line-height: var(--lh-tight);
}
.dlg-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.dlg-btn-primary {
  background: var(--accent);
  color: var(--text-on-accent);
  border-color: var(--accent);
}
.dlg-btn-primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent) 85%, #000);
}

.dlg-btn-danger {
  background: var(--danger);
  color: var(--text-on-status);
  border-color: var(--danger);
}
.dlg-btn-danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 85%, #000);
}

.dlg-btn-ghost {
  background: transparent;
  color: var(--text-muted);
  border-color: transparent;
}
.dlg-btn-ghost:hover:not(:disabled) {
  background: var(--surface-2);
  color: var(--text);
}

.dlg-btn-outline {
  background: transparent;
  color: var(--text);
  border-color: var(--border);
}
.dlg-btn-outline:hover:not(:disabled) {
  background: var(--surface-2);
  border-color: var(--text-dim);
}

/* ─── Save Spinner ─── */
/* currentColor, not white: ui-dialog injects this INTO the save button
   (ui-dialog.js:576), which is `variant="primary"` in every authored dialog and
   therefore an --accent fill. A hardcoded white ring was invisible on trident's
   lime and greenscreen's phosphor. currentColor also keeps it correct in the
   ghost/outline variants, where --text-on-accent would be the wrong color —
   which is why this one site uses currentColor rather than the token. */
.dlg-spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid color-mix(in srgb, currentColor 25%, transparent);
  border-top-color: currentColor;
  border-radius: var(--radius-circle);
  animation: dlg-spin 0.6s linear infinite;
  vertical-align: middle;
}
@keyframes dlg-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ══════════════════════════════════════════════════════════════════
   DETAIL — ui-detail-hero (v2 — fully declarative)
   ══════════════════════════════════════════════════════════════════ */

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) {
  display: block;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-v2 {
  display: flex;
  flex-direction: column;
  gap: 0;
  /* Horizontal padding is --spacing-7 (16px), not --spacing-8 (24px), so the hero's
     content sits on the same left edge as the detail sidebar's nav ICONS below it.
     Measured: sidebar left 260, nav icon left 276. At 24px the hero started at 284
     and everything in it was 8px out of alignment with the navigation. */
  padding: var(--spacing-5) var(--spacing-7);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

/* Row 1: Title + Actions */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-row-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-6);
  min-height: 32px;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-title-group {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  min-width: 0;
  flex: 1;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-title {
  font-size: var(--fs-title);
  font-weight: var(--fw-bold);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 0;
  line-height: var(--lh-tight);
  /* Ellipsis inside a flex row needs an explicit min-width, or the title refuses
     to shrink and pushes the status badge out of the row instead. */
  min-width: 0;
}

/* Window controls (sidebar + collapse), leading the row left of the title.
   Kept as its own cluster with a divider so it reads as chrome rather than as part
   of the entity's identity — in the original the panel glyph sat flush against the
   insured's name and was mistaken for a logo. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chrome {
  display: flex;
  align-items: center;
  gap: var(--spacing-1);
  flex-shrink: 0;
  padding-right: var(--spacing-4);
  margin-right: var(--spacing-2);
  border-right: 1px solid var(--border);
}

/* The status badge shares a flex line with the collapsible body rather than being
   pinned to .hero-v2's corner, so the hero is always at least as tall as the badge.
   Pinning it made the hero's height its containing block's problem: with no subtitle,
   no vitals and no banner all three body rows are display:none, and the badge lands
   in .hero-row-top's band on top of the action buttons — as does every hero whose
   body is collapsed.

   :empty drops the slot out of the flow rather than leaving a zero-width flex item,
   whose `gap` would narrow the vitals band on every hero that renders its status as
   a vital card instead of a pinned pill. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-lower {
  display: flex;
  align-items: flex-end;
  gap: var(--spacing-6);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-lower > .hero-body {
  flex: 1;
  min-width: 0;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-status-badge-slot {
  display: inline-flex;
  flex-shrink: 0;
  /* Clearance from .hero-row-top for the case where the badge is the tallest thing
     on this line and would otherwise butt against the action buttons. Bottom-aligned,
     so it moves the badge on no hero that has a body row taller than the pill. */
  margin-top: var(--spacing-3);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-status-badge-slot:empty {
  display: none;
}
/* Scoped to `.badge` so it styles only the PINNED pill (the no-vitals case). The same
   hero-status-badge hook is also on the status CARD's value, which must keep the card
   value's own type and zero padding. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-status-badge.badge {
  flex-shrink: 0;
  font-size: var(--fs-sm);
  padding: var(--spacing-1) var(--spacing-4);
}

/* Same clearance and bottom alignment as the status slot it sits beside, so a hero
   with chips and no status does not pull them up into .hero-row-top's band. Wraps
   rather than overflowing: the count is open-ended and the action buttons are not
   allowed to move. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chips {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-3);
  flex-shrink: 0;
  max-width: 40%;
  margin-top: var(--spacing-3);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chips:empty {
  display: none;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chip.badge {
  gap: var(--spacing-2);
  font-size: var(--fs-sm);
  padding: var(--spacing-1) var(--spacing-4);
}
/* An untoned chip is still a pill. .badge alone has no surface, which on this row
   would read as loose text beside the toned ones rather than as a chip. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chip-neutral {
  background: var(--surface-2);
  color: var(--text-muted);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chip-icon {
  display: inline-flex;
  align-items: center;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-toggle {
  flex-shrink: 0;
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--spacing-2);
  color: var(--text-muted);
  border-radius: var(--radius-xs);
  display: flex;
  align-items: center;
  transition: color 0.15s, background 0.15s;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-toggle:hover {
  color: var(--accent);
  background: var(--surface-2);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-sidebar-toggle {
  flex-shrink: 0;
  background: none;
  border: none;
  cursor: pointer;
  padding: var(--spacing-2);
  color: var(--text-muted);
  border-radius: var(--radius-xs);
  display: flex;
  align-items: center;
  transition: color 0.15s, background 0.15s;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-sidebar-toggle:hover {
  color: var(--accent);
  background: var(--surface-2);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions {
  display: flex;
  /* --spacing-3 (6px), not --spacing-1 (2px). Now that every action is a 30px icon
     square, 2px ran them together into one undifferentiated blob — the gap was set
     when the buttons carried labels and were self-separating. */
  gap: var(--spacing-3);
  flex-shrink: 0;
  align-items: center;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  color: var(--text-muted);
  background: transparent;
  transition:
    background 0.15s,
    color 0.15s;
  position: relative;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-icon-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
/* Labeled action buttons.
   The old .hero-icon-btn.btn-* rules set COLOR ONLY — btn-primary was an accent
   hue, btn-outline resolved identical to the default, and there was no size, fill
   or border difference. Hue was the entire hierarchy signal, which is both
   unreadable at a glance and WCAG 1.4.1 use-of-color. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  height: 30px;
  padding: 0 var(--spacing-6);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: var(--lh-none);
  white-space: nowrap;
  transition:
    background 0.15s,
    color 0.15s,
    border-color 0.15s;
}

/* Icon-only secondary: same 30px square as the kebab trigger, so the row reads as
   one control cluster rather than a mix of pills and glyphs. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn.hero-action-icon-only {
  width: 30px;
  padding: 0;
  justify-content: center;
  gap: 0;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn.btn-primary:not([aria-disabled='true']):hover {
  background: color-mix(in srgb, var(--accent) 85%, black);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn.btn-outline {
  background: var(--surface-2);
  border-color: var(--border);
  color: var(--text);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn.btn-outline:not([aria-disabled='true']):hover {
  background: var(--surface-3);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn.btn-danger {
  background: var(--danger-bg);
  border-color: color-mix(in srgb, var(--danger) 35%, transparent);
  color: var(--danger);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn.btn-danger:not([aria-disabled='true']):hover {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
}

/* A DISABLED HERO ACTION, AND WHAT IS DELIBERATELY ABSENT FROM THIS RULE.

   No `pointer-events: none`. a4eadc0d removed exactly that from `.uib-disabled`
   because it broke the whole point of shipping a button disabled with its reason
   on it: elementFromPoint at the button's center returned the host element, no
   :hover ever landed, and a native `title` needs hover — so every stated reason
   was unreadable by any user, and the `cursor: not-allowed` beside it was dead for
   the same reason. Reintroducing it here would be the same trap in the same month.

   The button is aria-disabled rather than natively disabled (see ui-hero-action's
   schema), so it also keeps its place in the tab order and its focus ring, and
   ui-detail-hero refuses the click in JS. Opacity plus the cursor carries the
   state; the tooltip carries the reason. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn[aria-disabled='true'] {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Matches the outline/offset convention used for every other focusable control. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-action-btn:focus-visible,
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-actions .hero-icon-btn:focus-visible,
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-menu-dropdown .menu-item:focus-visible,
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-toggle:focus-visible,
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-sidebar-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Action menu dropdown */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-action-menu {
  position: relative;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-action-menu .hero-menu-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: var(--spacing-2);
  /* `min-width` is a FLOOR, and `max-width` a ceiling — the width between them is
     set by the longest label, via the shrink-to-fit sizing this box already has.
     That only works because `.menu-item` below is `nowrap`: an absolutely
     positioned box shrink-to-fits to max(min-content, available), and `available`
     here is the width of `.hero-action-menu`, which is one 28px kebab button. With
     wrappable text, min-content is the longest WORD, so every menu collapsed to
     the 160px floor and any label longer than that wrapped to two lines
     (measured: "Delete Claim permanently" 43px tall against 28px for its
     siblings). The ceiling keeps a pathological or pseudo-localized label from
     growing leftward off the viewport, since the box is anchored `right: 0`. */
  min-width: 160px;
  max-width: min(24rem, calc(100vw - var(--spacing-8)));
  padding: var(--spacing-2) 0;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--elev-menu);
  z-index: var(--z-menu);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-action-menu.open .hero-menu-dropdown {
  display: block;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-menu-dropdown .menu-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-md);
  color: var(--text);
  cursor: pointer;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
  /* Load-bearing for the dropdown's width, not just for this row — see the
     `min-width`/`max-width` note on `.hero-menu-dropdown`. `nowrap` makes
     min-content equal max-content, which is what lets shrink-to-fit size the menu
     to its longest label instead of to its longest word.

     `overflow: hidden` is what makes that `max-width` a real ceiling. Without it
     the BOX stopped at 24rem and the TEXT did not — a label past the ceiling
     painted straight out over whatever sat beside the menu, which is a worse
     failure than the wrapping this replaced, because it does not look like a
     layout fault, it looks like someone else's row. It clips this item's own
     content only: an `outline` is painted by the element rather than being part of
     its content, so the `:focus-visible` ring above is unaffected, and clipping
     here rather than on `.hero-menu-dropdown` is what preserves it.

     `min-width: 0` because this is a flex container — a flex item's automatic
     minimum size is its content, which would win over the clip and reinstate the
     overflow. No authored label is anywhere near 24rem (the longest in the corpus
     measures ~221px, and ~135% pseudo-locale expansion still clears it), so this
     guards a case that is latent rather than live. */
  white-space: nowrap;
  overflow: hidden;
  min-width: 0;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-menu-dropdown .menu-item:hover {
  background: var(--surface-3);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-menu-dropdown .menu-item.danger {
  color: var(--danger);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-menu-dropdown .menu-separator {
  height: 1px;
  background: var(--border);
  margin: var(--spacing-2) 0;
}

/* Collapsible body */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-body {
  overflow: hidden;
  max-height: 200px;
  transition:
    max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1);
  opacity: 1;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-body.collapsed {
  max-height: 0;
  opacity: 0;
  pointer-events: none;
  transition:
    max-height 0.25s cubic-bezier(0.4, 0, 1, 1),
    opacity 0.15s cubic-bezier(0.4, 0, 1, 1);
}

/* Row 2: Subtitle + Status badge */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-subtitle-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  flex-wrap: wrap;
  font-size: var(--fs-base);
  color: var(--text-muted);
  padding: var(--spacing-1) 0 0;
  min-height: 0;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-subtitle-row.empty {
  display: none;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-sep {
  color: var(--text-dim);
  font-weight: var(--fw-light);
  margin: 0 var(--spacing-1);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-sub-part {
  white-space: nowrap;
}

/* Row 3: Meta cards (compact) */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-row {
  display: flex;
  gap: var(--spacing-4);
  flex-wrap: wrap;
  padding: var(--spacing-3) 0 var(--spacing-1);
  min-height: 0;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-row.empty {
  display: none;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  padding: var(--spacing-3) var(--spacing-5);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  min-width: 70px;
  /* Cards GROW from their content width to fill the band, rather than clustering at
     the left. The vitals row was using 35% of a 1119px hero, so the actions sat
     marooned at the right with a void under them — the layout read as unbalanced
     wherever the status badge went.

     `1 1 auto`, not `1 1 0`: equal-basis flex made every card the same width, which
     cramped "POL-2026-00012" into two lines while wasting the same space on "KS".
     Growing from content keeps each card proportional to what it holds.

     max-width caps the growth so a hero with only two vitals gets two reasonable
     cards rather than two 550px slabs. */
  flex: 1 1 auto;
  max-width: 240px;
}

/* ── a vital that is a control, not a value (ND-901) ──────────────────────────
   A <button> reset back to the card's own box, so a toned clickable card is visually a
   card and behaviorally a button. `font: inherit` and `text-align: left` are the two
   the UA stylesheet would otherwise win: without them the label centers and drops to
   13px, which is what makes a styled button read as "not quite one of these".

   `color: inherit` is the THIRD, and it was missing. The UA's `buttontext` won, so an
   untoned nav card rendered pure #000 while every plain card beside it rendered
   var(--text) — measured live as rgb(0,0,0) against rgb(26,29,39). Invisible on a dark
   brand, and the kind of hardcoded color invariant 5 exists to refuse, arriving via
   the UA stylesheet rather than via a declaration. Found while adding `route`
   (ND-85), which renders through this same rule and would have doubled it.
   `inherit` rather than var(--text) so the toned rule below — two classes, so it
   still wins on specificity — keeps painting danger/warning/success. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) button.hero-meta-nav {
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) button.hero-meta-nav:hover {
  border-color: var(--accent);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-arrow {
  margin-left: var(--spacing-3);
}

/* One line per value. A card that grows is a card that can also be squeezed, and a
   wrapped value changes the row's height and therefore the whole hero's. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-value {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The status card. Tinted from its own variant color via currentColor, so the five
   variants need one rule each instead of three. */
/* The tint. Applies to the status card and to any vital that asked for a tone via
   `variant` — hence the marker class rather than widening to .hero-meta-card, which
   would tint EVERY vital with currentColor and repaint the whole band. (ND-901) */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card:is(.hero-status-card, .hero-meta-toned) {
  border-color: color-mix(in srgb, currentColor 45%, transparent);
  background: color-mix(in srgb, currentColor 12%, transparent);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card:is(.hero-status-card, .hero-meta-toned) .hero-meta-label,
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card:is(.hero-status-card, .hero-meta-toned) .hero-meta-value {
  color: currentColor;
}
/* The hue itself is keyed on the tone class alone, so ONE vocabulary serves both. A
   card carrying no status-* class matches none of these, which is why widening these
   five is safe where widening the two above is not. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card.status-muted { color: var(--text-muted); }
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card.status-info { color: var(--info); }
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card.status-success { color: var(--success); }
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card.status-warning { color: var(--warning); }
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-card.status-danger { color: var(--danger); }

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-label {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-icon {
  display: inline-flex;
  color: var(--text-dim);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-meta-value {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

/* Row 4: Banners */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner-row {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  padding-top: var(--spacing-3);
  min-height: 0;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner-row.empty {
  display: none;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-5);
  border-radius: var(--radius-sm);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner-icon {
  display: inline-flex;
  flex-shrink: 0;
}

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner-info {
  background: var(--info-bg);
  color: var(--info);
  border: 1px solid color-mix(in srgb, var(--info) 25%, transparent);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner-warning {
  background: var(--warning-bg);
  color: var(--warning);
  border: 1px solid color-mix(in srgb, var(--warning) 25%, transparent);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner-error {
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 25%, transparent);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-banner-success {
  background: var(--success-bg);
  color: var(--success);
  border: 1px solid color-mix(in srgb, var(--success) 25%, transparent);
}

/* ══════════════════════════════════════════════════════════════════
   DETAIL — ui-node-header (v2 — compact node identity bar)
   ══════════════════════════════════════════════════════════════════ */

ui-node-header {
  display: block;
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
}

ui-node-header .nh-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-7);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  min-height: 34px;
}

/* ── Left: back + title + badges + meta ── */
ui-node-header .nh-left {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  min-width: 0;
  flex: 1;
  overflow: hidden;
}

ui-node-header .nh-back {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
  color: var(--text-muted);
  background: transparent;
  transition:
    background 0.15s,
    color 0.15s;
}
ui-node-header .nh-back:hover {
  background: var(--surface-2);
  color: var(--text);
}

ui-node-header .nh-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: var(--lh-tight);
  flex-shrink: 1;
  min-width: 0;
}

ui-node-header .nh-badges {
  display: inline-flex;
  gap: var(--spacing-2);
  flex-shrink: 0;
}

/* Small badges for node header */
ui-node-header .badge-sm {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-xs);
  line-height: var(--lh-normal);
  white-space: nowrap;
}

/* Casing normalization is OPT-IN, added by ui-node-header per value.
   `text-transform` is content-blind — CSS cannot tell an enum token from a
   sentence — so it sat here as a blanket `capitalize` and re-wrote server-composed
   text: `Ready to issue, but the rate is $0` rendered as
   `Ready To Issue, But The Rate Is $0`, a warning restyled into a label. The
   element decides per value (see normalizesCase) and only all-lowercase values,
   which carry no casing of their own, get this class. Everything else renders
   exactly as the system stated it. */
ui-node-header .badge-sm.badge-cap {
  text-transform: capitalize;
}
ui-node-header .badge-sm.success {
  background: var(--success-bg);
  color: var(--success);
}
ui-node-header .badge-sm.warning {
  background: var(--warning-bg);
  color: var(--warning);
}
ui-node-header .badge-sm.info {
  background: var(--info-bg);
  color: var(--info);
}
ui-node-header .badge-sm.danger {
  background: var(--danger-bg);
  color: var(--danger);
}
ui-node-header .badge-sm.muted {
  background: var(--surface-2);
  color: var(--text-muted);
}

ui-node-header .nh-meta {
  font-size: var(--fs-base);
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex-shrink: 2;
  min-width: 0;
}
ui-node-header .nh-meta::before {
  content: '·';
  margin-right: var(--spacing-3);
  color: var(--text-dim);
}

/* ── Right: actions ── */
ui-node-header .nh-right {
  display: flex;
  flex-shrink: 0;
  align-items: center;
}

ui-node-header .nh-actions {
  display: flex;
  gap: var(--spacing-1);
  align-items: center;
}

ui-node-header .nh-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
  color: var(--text-muted);
  background: transparent;
  transition:
    background 0.15s,
    color 0.15s;
}
ui-node-header .nh-icon-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
ui-node-header .nh-icon-btn.nh-btn-primary {
  color: var(--accent);
}
ui-node-header .nh-icon-btn.nh-btn-primary:hover {
  background: var(--accent-dim);
}
ui-node-header .nh-icon-btn.nh-btn-danger {
  color: var(--danger);
}
ui-node-header .nh-icon-btn.nh-btn-danger:hover {
  background: var(--danger-bg);
}

/* Action divider between contextual and persistent */
ui-node-header .nh-divider {
  display: inline-block;
  width: 1px;
  height: 16px;
  background: var(--border);
  margin: 0 var(--spacing-2);
  flex-shrink: 0;
}

/* ── Kebab menu ── */
ui-node-header .nh-menu {
  position: relative;
}
ui-node-header .nh-menu-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: var(--spacing-2);
  min-width: 150px;
  padding: var(--spacing-2) 0;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--elev-menu);
  z-index: var(--z-menu);
}
ui-node-header .nh-menu.open .nh-menu-dropdown {
  display: block;
}
ui-node-header .nh-menu-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
  color: var(--text);
  cursor: pointer;
  border: none;
  background: none;
  width: 100%;
  text-align: left;
}
ui-node-header .nh-menu-item:hover {
  background: var(--surface-3);
}
ui-node-header .nh-menu-item.danger {
  color: var(--danger);
}
ui-node-header .nh-menu-separator {
  height: 1px;
  background: var(--border);
  margin: var(--spacing-2) 0;
}

/* ══════════════════════════════════════════════════════════════════
   DETAIL — ui-detail-sidebar (tree-capable)
   ══════════════════════════════════════════════════════════════════ */

:is(ui-detail-sidebar, ui-policy-nav) {
  display: block;
  overflow-y: auto;
  font-size: var(--fs-md);
}

/* ── Sections ─────────────────────────────────────────────────── */

:is(ui-detail-sidebar, ui-policy-nav) .sb-section {
  margin-bottom: var(--spacing-2);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-section-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-4) var(--spacing-4) var(--spacing-3);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-muted);
  cursor: pointer;
  user-select: none;
  border-radius: var(--radius-xs);
  transition: color 0.12s;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-section-header:hover {
  color: var(--text);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-section-icon {
  display: flex;
  align-items: center;
  color: var(--text-dim);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-section-label {
  flex: 1;
  min-width: 0;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-chevron {
  font-size: var(--fs-3xs);
  transition: transform 0.15s ease;
  color: var(--text-dim);
  display: inline-block;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-chevron.expanded {
  transform: rotate(90deg);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-section-body {
  overflow: hidden;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-section-body.collapsed {
  display: none;
}

/* ── Flat Nav Items ───────────────────────────────────────────── */

:is(ui-detail-sidebar, ui-policy-nav) .sb-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-5);
  margin: var(--spacing-1) var(--spacing-2);
  font-size: var(--fs-md);
  cursor: pointer;
  color: var(--text-muted);
  border-radius: var(--radius-sm);
  transition:
    background 0.12s,
    color 0.12s;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-item:hover {
  background: var(--surface-2);
  color: var(--text);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-item.active {
  background: var(--accent-dim);
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-item-icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-item-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Badges ───────────────────────────────────────────────────── */

:is(ui-detail-sidebar, ui-policy-nav) .sb-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 var(--spacing-3);
  border-radius: var(--radius-full);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  line-height: var(--lh-none);
  flex-shrink: 0;
  background: var(--surface-2);
  color: var(--text-muted);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-badge-success {
  background: var(--success-bg);
  color: var(--success);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-badge-danger {
  background: var(--danger-bg);
  color: var(--danger);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-badge-warning {
  background: var(--warning-bg);
  color: var(--warning);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-badge-info {
  background: var(--info-bg);
  color: var(--info);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-badge-money {
  background: var(--success-bg);
  color: var(--success);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-badge-muted {
  background: rgba(139, 143, 165, 0.1);
  color: var(--text-dim);
}

/* ── Section state ────────────────────────────────────────────── */

/* The disc and the ring take their color from the row so one token per state
   drives both marks. The tick and the dot are punched out in --surface, which is
   the rail's own background: they must read against the DISC, not against the row,
   so they do not follow the active row's accent tint. */
:is(ui-detail-sidebar, ui-policy-nav) .sb-state {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state svg {
  display: block;
  width: 14px;
  height: 14px;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state-complete {
  color: var(--success);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state-attention {
  color: var(--warning);
}

/* --text-muted, not --text-dim: the dashed ring is a 1px stroke in 1.75px dashes at
   14px, and --text-dim measured 2.72:1 on the rail — under the 3:1 WCAG floor for a
   non-text mark, where it read as a faint spinner rather than a ring. */
:is(ui-detail-sidebar, ui-policy-nav) .sb-state-not-started {
  color: var(--text-muted);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state .sb-state-disc {
  fill: currentColor;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state .sb-state-mark {
  fill: none;
  stroke: var(--surface);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state .sb-state-dot {
  fill: var(--surface);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state .sb-state-ring {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.5;
  stroke-dasharray: 3 3;
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-state-legend {
  display: grid;
  gap: var(--spacing-2);
  margin: var(--spacing-5) var(--spacing-5) var(--spacing-3);
  font-size: var(--fs-xs);
  color: var(--text-dim);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-legend-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}

/* ── Tree ─────────────────────────────────────────────────────── */

:is(ui-detail-sidebar, ui-policy-nav) .sb-tree {
  padding: 0 0 var(--spacing-2);
}

:is(ui-detail-sidebar, ui-policy-nav) .sb-tree-empty {
  padding: var(--spacing-4) var(--spacing-6);
  font-size: var(--fs-base);
  color: var(--text-dim);
  font-style: italic;
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-4);
  padding-left: calc(8px + var(--depth, 0) * 16px);
  margin: var(--spacing-1) var(--spacing-2);
  font-size: var(--fs-md);
  cursor: pointer;
  color: var(--text-muted);
  border-radius: var(--radius-sm);
  transition:
    background 0.12s,
    color 0.12s;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-row:hover {
  background: var(--surface-2);
  color: var(--text);
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-row.active {
  background: var(--accent-dim);
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  font-size: var(--fs-3xs);
  flex-shrink: 0;
  transition: transform 0.15s ease;
  color: var(--text-dim);
  cursor: pointer;
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-chevron.expanded {
  transform: rotate(90deg);
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-chevron.leaf {
  visibility: hidden;
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

:is(ui-detail-sidebar, ui-policy-nav) .tree-label-muted {
  color: var(--text-dim);
}

/* ══════════════════════════════════════════════════════════════════
   DETAIL — ui-detail-view layout
   ══════════════════════════════════════════════════════════════════ */

ui-detail-view {
  display: grid;
  grid-template-columns: var(--detail-sidebar-width, 240px) 1fr;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 0;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

ui-detail-view:has(> :is(ui-detail-sidebar, ui-policy-nav)[collapsed]) {
  grid-template-columns: 0px 1fr;
}

/* A view that authors NO nav region has no column to reserve for one. Without
   this the content region still starts 240px in, with dead space beside it —
   the fixed-width nothing a one-item rail gets deleted to remove. Same
   mechanism as the collapsed rule above, and keyed on :has() so all 25 authored
   nav-driven views compute byte-identically. */
ui-detail-view:not(:has(> :is(ui-detail-sidebar, ui-policy-nav))) {
  grid-template-columns: 0px 1fr;
}

ui-detail-view {
  transition: grid-template-columns 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

ui-detail-view.detail-sidebar-resizing {
  user-select: none;
  cursor: col-resize;
}

ui-detail-view > :is(ui-detail-hero, ui-policy-hero, ui-policy-header) {
  grid-column: 1 / -1;
  /* PINNED, and it was not. An unplaced child takes row 1 / column 1, and the hero
     spans 1/-1 so it cannot share that row — it gets pushed to an implicit row
     BELOW the content. That has been observed twice: once with this element's own
     .dv-shell-fallback banner (see the [shell-fallback] block near the end of this
     file, which fixes it point-wise by renumbering all three rows) and once with an
     authored <ui-card>. Naming the row here fixes the cause rather than the
     instance: nothing an author or a future banner adds can displace the hero. */
  grid-row: 1;
}

/* ANY OTHER DIRECT CHILD — full width, never a column.
   ══════════════════════════════════════════════════════════════════
   ui-detail-view declares exactly four regions in its `permittedContent`
   (hero, sidebar, content, and ui-policy-hero/ui-policy-nav as the policy
   spellings of two of them). Everything else authored at this level is
   UNPLACED, and grid auto-placement drops an unplaced item into the first free
   cell — row 3, COLUMN 1, i.e. the 240px sidebar column. Measured on a 1600×1150
   view with one authored <ui-card> of action buttons: the card wrapped into a
   240px stack 415px tall, the 1360px band beside it was empty
   (elementFromPoint(1200, 900) hit the bare ui-detail-view), and the content
   region was squeezed from 1060px to 735px with its overflow behind a nested
   scroller. A third of the screen, dead.

   This is NOT here to bless the pattern — ui-detail-view.js reports every such
   child by name in the console, and the honest fix is to move it inside a
   ui-content-panel. It is here so that the failure mode is a visible full-width
   band rather than a silent dead region, and so the content area only loses the
   height the stray actually needs.

   Measured scope before writing this: 201 authored v3 HTML files across the
   workspace, 26 of which mount ui-detail-view. Their direct children are
   ui-dialog ×64, ui-detail-sidebar ×25, ui-detail-content ×25, ui-detail-hero ×22,
   ui-actions ×17, ui-policy-hero ×4 and ui-card ×1. Every one of those except the
   single ui-card is either a region placed above or `display: none`/`contents`
   (ui-actions, ui-dialog), which are not grid items at all — so this rule changes
   the rendering of exactly one file in the corpus, plus the two SDK banners below,
   which it also stops rendering 240px wide. */
ui-detail-view > *:not(ui-detail-hero, ui-policy-hero, ui-policy-header, ui-policy-facts, ui-detail-actions, ui-detail-sidebar, ui-policy-nav, ui-detail-content) {
  grid-column: 1 / -1;
}

/* The vitals strip, in a plain detail view rather than a workspace: a full-width
   band between the hero and the working area, which pushes the body down a row.
   Keyed on :has() exactly as the ui-detail-actions block below is, so the 26
   authored detail views that carry no strip compute byte-identically. */
ui-detail-view:has(> ui-policy-facts) {
  grid-template-rows: auto auto minmax(0, 1fr);
}
ui-detail-view > ui-policy-facts {
  grid-column: 1 / -1;
  grid-row: 2;
}
ui-detail-view:has(> ui-policy-facts) > :is(ui-detail-sidebar, ui-policy-nav),
ui-detail-view:has(> ui-policy-facts) > ui-detail-content {
  grid-row: 3;
}
ui-detail-view:has(> ui-policy-facts):has(> ui-detail-actions) {
  grid-template-rows: auto auto auto minmax(0, 1fr);
}
ui-detail-view:has(> ui-policy-facts) > ui-detail-actions {
  grid-row: 3;
}
ui-detail-view:has(> ui-policy-facts):has(> ui-detail-actions) > :is(ui-detail-sidebar, ui-policy-nav),
ui-detail-view:has(> ui-policy-facts):has(> ui-detail-actions) > ui-detail-content {
  grid-row: 4;
}

/* ── ui-detail-actions — THE ENTITY-LEVEL ACTION BAR, A PLACED REGION ─────────
   ══════════════════════════════════════════════════════════════════
   The sanctioned home for the verbs that belong to the RECORD rather than to one
   panel, and the answer to what the rule above merely makes survivable. An
   authored <ui-card> of buttons at this level is not just misplaced: nothing
   binds it, so every show-when inside it is unevaluable and therefore VISIBLE,
   and a state-gated bar of ten verbs renders all ten whatever the record says.
   The element's own header carries the four alternatives that were rejected.

   FULL WIDTH, AND DIRECTLY UNDER THE HERO — which is where an author writing it
   between the hero and the sidebar already means, and where the catch-all above
   cannot put it (auto-placement can only append). The working area moves down a
   row, exactly the way the [shell-fallback] state at the end of this file already
   renumbers, and by the same mechanism.

   KEYED ON :has(), so a view without the region computes byte-identically to
   before: none of the 26 authored ui-detail-view files carries the tag, so this
   whole block is inert across the corpus on the day it ships. */
ui-detail-view:has(> ui-detail-actions) {
  grid-template-rows: auto auto minmax(0, 1fr);
}
ui-detail-view > ui-detail-actions {
  grid-column: 1 / -1;
  grid-row: 2;
}
ui-detail-view:has(> ui-detail-actions) > :is(ui-detail-sidebar, ui-policy-nav),
ui-detail-view:has(> ui-detail-actions) > ui-detail-content {
  grid-row: 3;
}

/* ── THE NAV REGION AS A TAB STRIP — orientation="horizontal" ────────────────
   ══════════════════════════════════════════════════════════════════
   The rail reserves `var(--detail-sidebar-width, 240px)` whether or not it is
   collapsed: the [collapsed] rule above sets that TRACK to 0px, so the column
   still exists and still transitions. For a view with two or three panels that
   is a third of a narrow screen spent on two words, plus a collapse control
   that is a second thing to learn.

   So this mode takes no track at all — the grid drops to one column and the
   strip spans it, in its own row between the hero and the content.

   THE ROW NUMBERS ARE THE WHOLE RISK. An unplaced grid item lands in the first
   free cell, which is how an authored <ui-card> once ate a 240px column and how
   this element's own banners ended up below the content. Both regions are
   pinned, and the ui-detail-actions combination is pinned too: with actions
   present there are four rows, not three, and a strip left on the auto row would
   share a cell with the action bar.

   Keyed on :has() with the attribute, so all 25 authored ui-detail-sidebar files
   compute byte-identically to before — none carries orientation. */
ui-detail-view:has(> ui-detail-sidebar[orientation='horizontal']) {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr);
}

ui-detail-view > ui-detail-sidebar[orientation='horizontal'] {
  grid-column: 1 / -1;
  grid-row: 2;
  min-width: 0;
}

ui-detail-view:has(> ui-detail-sidebar[orientation='horizontal']) > ui-detail-content {
  grid-column: 1 / -1;
  grid-row: 3;
}

ui-detail-view:has(> ui-detail-actions):has(> ui-detail-sidebar[orientation='horizontal']) {
  grid-template-rows: auto auto auto minmax(0, 1fr);
}
ui-detail-view:has(> ui-detail-actions) > ui-detail-sidebar[orientation='horizontal'] {
  grid-row: 3;
}
ui-detail-view:has(> ui-detail-actions):has(> ui-detail-sidebar[orientation='horizontal'])
  > ui-detail-content {
  grid-row: 4;
}

/* No border-right, no resize cursor, no min-width — every rail affordance the
   vertical block sets has to be undone, or the strip carries a 1px divider down
   its left edge and a 180px floor.

   THE PADDING RESET IS AN ALIGNMENT FIX, NOT TIDYING. `ui-detail-view >
   :is(ui-detail-sidebar, ui-policy-nav)` sets padding: var(--spacing-4), which is
   the rail's inset; left on the strip it stacked with the bar's own
   var(--spacing-7) for a 24px inset, putting the first tab's box at x=284 while
   .hero-row-top above it sits at 276. Measured on the Designer at 1626px.

   Eight pixels, and the same eight the .hero-v2 comment above already records
   fighting: that block chose --spacing-7 over --spacing-8 precisely so the hero
   content and the navigation share one left edge. Zeroing here and keeping the
   16px on the bar puts the tab box back on 276.

   SHARED SURFACE. The hero is var(--surface) and the strip was transparent over
   var(--bg), so the two read as a header and then a separate toolbar. On one
   surface with one rule at the bottom it reads as a header with a tab row —
   which is what "aligned with the hero-row-top above" asks for beyond the
   left edge. */
ui-detail-sidebar[orientation='horizontal'] {
  display: block;
  width: auto;
  min-width: 0;
  max-width: none;
  padding: 0;
  border-right: none;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  overflow: visible;
  resize: none;
}

/* .tabs-bar is deliberately ui-tabs' own class, so this is the platform's
   existing tab strip rather than a second one that looks nearly like it. Only
   the gutter differs: it lines up with the hero above and the content below. */
/* var(--spacing-7) horizontally — the same 16px .hero-v2 uses — so the first
   tab's BOX lands on .hero-row-top's left edge. The box, not the label: these
   render as buttons now, and a bordered control aligns on its border, which is
   the edge the eye actually follows down the page.

   No border-bottom: the host carries the one rule, at the bottom of the whole
   header block. Two rules 45px apart is what made the strip read as its own
   floating band. */
ui-detail-sidebar[orientation='horizontal'] .sb-tabs-bar {
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-7);
  border-bottom: none;
  overflow-x: auto;
  scrollbar-width: none;
}

ui-detail-sidebar[orientation='horizontal'] .sb-tabs-bar::-webkit-scrollbar {
  display: none;
}

/* ── THE TABS ARE BUTTONS ────────────────────────────────────────────────────
   ══════════════════════════════════════════════════════════════════
   ui-button's language, token for token: .uib's border, radius, weight and
   line-height, .uib-sm's padding and size, .uib-outline's transparent fill and
   muted text with an accent border on hover. The same combination
   .ff-lookup-btn reuses, and that site's comment already argues the case — a
   bespoke skin would be "a fourth near-miss on the same focus ring and the same
   padding scale".

   THE TOKENS ARE NAMED HERE RATHER THAN THE CLASSES ADDED IN JS, and that is a
   cascade fact, not a preference: .tabs-tab is declared ~7000 lines after .uib
   at equal specificity, so `class="uib uib-sm tabs-tab"` resolves to .tabs-tab's
   `border: none` and square-topped radius — the button look would silently not
   apply. This selector is (0,2,1) and wins outright.

   NOT .tabs-pills, which is the nearer existing variant: pills wrap the whole
   bar in a --surface box with its own border and radius, which is a segmented
   control and would draw a box inside the hero's surface — precisely the
   separateness request 2 is about removing. Pills' selected state is also a
   borderless --accent-dim fill, which is a pill; this app's buttons have a
   border, and "resemble buttons" is what was asked.

   The selected state takes pills' FILL logic inside ui-button's shape:
   --accent-dim rather than a solid --accent, because a filled accent tab would
   compete with the hero's primary Run action two rows up, and there cannot be
   two primaries on one screen. */
ui-detail-sidebar[orientation='horizontal'] .sb-item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  width: auto;
  margin: 0;
  padding: var(--spacing-3) var(--spacing-6);
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-none);
}

ui-detail-sidebar[orientation='horizontal'] .sb-item:hover:not([aria-selected='true']) {
  color: var(--text);
  background: transparent;
  border-color: var(--accent);
}

ui-detail-sidebar[orientation='horizontal'] .sb-item[aria-selected='true'] {
  color: var(--accent);
  background: var(--accent-dim);
  border-color: var(--accent);
}

/* The underline belonged to a tab. A button carries its selection in its own
   border and fill, and an underline under a bordered box reads as an artifact. */
ui-detail-sidebar[orientation='horizontal'] .sb-item[aria-selected='true']::after {
  display: none;
}

/* ui-detail-view sets `loading` on whichever nav it found while the entity
   loads; the rail already honors it and the strip must too. */
ui-detail-sidebar[orientation='horizontal'][loading] {
  opacity: 0.5;
  pointer-events: none;
}

/* A BAR, NOT A CARD. It is a strip of the chrome between the hero and the work,
   so it takes the hero's surface and closes with the same hairline the nav column
   uses — a bordered card here would read as the first item of content and compete
   with the panel below it, which is precisely what the <ui-card> workaround did.
   Laid out as a wrapping row so the common case needs no <ui-stack> at all; nest
   one for any other arrangement. */
ui-detail-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
/* A region emptied by its own show-when must take no height. An empty strip with
   a border on it reads as a control bar that failed to load. */
ui-detail-actions:empty {
  display: none;
}

ui-detail-view > :is(ui-detail-sidebar, ui-policy-nav) {
  grid-column: 1;
  grid-row: 2;
  padding: var(--spacing-4);
  border-right: 1px solid var(--border);
  min-height: 0;
  overflow-y: auto;
  position: relative;
  transition:
    padding 0.3s cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}

ui-detail-view > :is(ui-detail-sidebar, ui-policy-nav)[collapsed] {
  padding: 0;
  overflow: hidden;
  opacity: 0;
  pointer-events: none;
  transition:
    padding 0.25s cubic-bezier(0.4, 0, 1, 1),
    opacity 0.2s cubic-bezier(0.4, 0, 1, 1);
}

ui-detail-view > :is(ui-detail-sidebar, ui-policy-nav) .detail-sb-resize-handle {
  position: absolute;
  top: 0;
  right: -2px;
  width: 4px;
  height: 100%;
  cursor: col-resize;
  z-index: var(--z-sticky);
  transition: background 0.15s;
}
ui-detail-view > :is(ui-detail-sidebar, ui-policy-nav) .detail-sb-resize-handle:hover,
ui-detail-view > :is(ui-detail-sidebar, ui-policy-nav) .detail-sb-resize-handle.dragging {
  background: var(--accent);
}

ui-detail-view > ui-detail-content {
  grid-column: 2;
  grid-row: 2;
  padding: 0;
  /* BELT, NOT BRACES — and measured, so the next reader does not credit it with
     more than it does. A grid item's automatic minimum size is its content's
     intrinsic width, which a table with explicit column widths blows past; the
     parent's `overflow: hidden` would then clip it. But that clamp applies only
     to items whose overflow is `visible`, and `overflow-y: auto` below already
     disqualifies this one. A/B'd live with a 1424px table in the claimants
     panel: with and without this line the pane stayed at 700px and .dt-scroll
     scrolled. Forced `overflow-y: visible` and the pane grew to 1426px WITHOUT
     this line and stayed at 700px WITH it. So it is here to keep the clamp if
     anyone ever changes the overflow, not because anything clips today. */
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
}

ui-content-panel {
  /* Was `display: block` here while ui-content-panel.onConnected set
     `display: flex` INLINE on every instance, so the declaration was dead and
     wrong — the rules just below rely on this being a flex column. The layout now
     lives here and the element no longer writes it, because that write was also
     racing the parent: every panel made ITSELF visible on connect while
     ui-detail-content hides the inactive ones, and connect order decided the
     winner. See ND-91. */
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

/* Panels MANAGED by a ui-detail-content are hidden until that parent activates
   one. Declared here rather than assigned as an inline style so that visibility
   has a single, declarative source of truth and a panel that connects LATE — the
   src= fetch is async — is hidden by default instead of appearing alongside the
   active one. Two panels really were visible together in that case.

   Scoped to the direct-child selector on purpose: ui-content-panel is also used
   standalone in two consumer pages (platform sdk-explorer module.html and
   app.html) with no ui-detail-content ancestor, and those must stay visible. A
   blanket `display: none` default would have blanked both. */
/* ND-91: these three declared their own display in onConnected, which is the same
   shape that made ui-content-panel's visibility non-deterministic. Nothing
   currently hides them, so it was a latent hazard rather than a live bug — but an
   element that writes its own display will fight any parent that later hides it.
   Only `display` moved: the gap/margin/height values beside it in JS are
   attribute-driven or would become density-responsive if tokenized here, and that
   is a separate decision. */
ui-field-group {
  display: grid;
}

/* The group heading. `grid-column: 1 / -1` is the load-bearing declaration:
   ui-field-group is a grid whose columns are set from the `columns` attribute,
   so without it the heading takes the first cell and sits beside the first
   key-value pair rather than above the group. */
ui-field-group .fg-label {
  grid-column: 1 / -1;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text);
}
ui-file-slot {
  display: flex;
  flex-direction: column;
}
ui-master-detail {
  display: flex;
  height: 100%;
}

ui-detail-content > ui-content-panel {
  display: none;
}
ui-detail-content > ui-content-panel.cp-active {
  display: flex;

  /* ND-956: THE ACTIVE PANEL OWNS ITS OWN SCROLL, so ui-node-header stays pinned.

     `ui-node-header` has been `position: sticky; top: 0` since f63f3d8, and the
     Edit/Save bar lives in it — but a sticky box cannot escape its containing
     block. The scroll owner was `ui-detail-view > ui-detail-content` while the
     header's containing block was this panel at `height: 100%`, i.e. exactly one
     scrollport tall. So the bar pinned until scrollTop passed
     (panelHeight - headerHeight) and was then pushed off by the bottom of the
     panel box and never came back. Measured on a PPA policy at a 700px
     scrollport: pinned to 660, gone at 700, and 765px above the scrollport by
     the end of a 1430px scroll.

     Making the panel the scroll container fixes it at the root: the sticky
     header's containing block becomes this panel's content box, which now spans
     the whole scrollable length, so it pins for the entire scroll.

     `height: 100%` is deliberately KEPT rather than relaxed to `min-height`.
     Growing the box would also fix the header, but it would take away the
     definite height that the nested scrollers just below depend on
     (`ui-content-panel > ui-tab-panels > ui-tab-panel { overflow-y: auto }`) —
     a tabbed panel would then grow instead of scrolling internally and its TABS
     would scroll away, which is this same defect moved one level down. Six of
     the eleven panels on the PPA policy view are tabbed.

     ui-detail-content keeps its own `overflow-y: auto`: it is the fallback
     scroller for anything that is not an active managed panel, and it does not
     double up, because a panel that is exactly 100% of it and scrolls
     internally leaves it with nothing to scroll (measured: outer overflow 0). */
  overflow-y: auto;
}

/* A panel whose own data-method FAILED (ND-824).

   The authored content is hidden while the banner is up, and that is deliberate:
   a panel of em-dashes under an error message is a plausible-looking render of
   data that was never received, which is the one shape this codebase treats as
   worse than a blank screen. The banner carries a Retry, so it is not a dead end.

   Hidden by a class plus this rule rather than by writing style.display in JS —
   invariant 7. `> *` and not a descendant selector, so nothing inside the banner
   is caught by it. */
ui-content-panel.cp-errored > *:not(.cp-error) {
  display: none;
}
ui-content-panel > .cp-error {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-3);
  margin: var(--spacing-6);
  padding: var(--spacing-6);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: var(--danger-bg);
}
.cp-error-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--danger);
}
.cp-error-detail {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-normal);
}
.cp-error-retry {
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.cp-error-retry:hover {
  background: var(--surface-3);
}

/* An element whose `src` FRAGMENT never arrived — BaseDeclarativeElement.
   _announceSrcFailure. Not the same failure as .cp-error above: that one is a
   data-method that refused, this one is the markup itself never loading, and an
   element in this state has no authored content at all to hide. So there is no
   `> *:not(...)` companion rule here — there is nothing to suppress.

   Deliberately not scoped to one tag: every element carrying a `src` can reach
   this state (ui-content-panel, ui-view, ui-module, ui-tab-panel), and until now
   all of them rendered an indistinguishable blank. */
[src-failed] > .sdk-src-error {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-3);
  margin: var(--spacing-6);
  padding: var(--spacing-6);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: var(--danger-bg);
}
.sdk-src-error-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--danger);
}
.sdk-src-error-detail {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-normal);
}
.sdk-src-error-url {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-dim);
  word-break: break-all;
}
.sdk-src-error-retry {
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.sdk-src-error-retry:hover {
  background: var(--surface-3);
}

/* Inside content panels, tabs stay fixed at top; tab-panels scroll */
ui-content-panel > ui-tabs {
  flex-shrink: 0;
}
ui-content-panel > ui-tab-panels {
  min-height: 0;
}
ui-content-panel > ui-tab-panels > ui-tab-panel {
  min-height: 0;
  overflow-y: auto;
}

/* ── Skeleton Loading ── */

@keyframes skeleton-shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

.skeleton-bar {
  border-radius: var(--radius-xs);
  background: linear-gradient(
    90deg,
    var(--surface-2) 0%,
    var(--surface-3) 40%,
    var(--surface-2) 80%
  );
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.8s ease-in-out infinite;
}

/* Hero skeleton bars */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-title {
  height: 20px;
  width: 45%;
  min-width: 160px;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-badge {
  height: 22px;
  width: 64px;
  border-radius: var(--radius-md);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-sub {
  height: 13px;
  border-radius: var(--radius-2xs);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-sub:nth-child(1) { width: 100px; }
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-sub:nth-child(2) { width: 140px; }
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-sub:nth-child(3) { width: 80px; }
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-meta {
  width: 80px;
  height: 40px;
  border-radius: var(--radius-sm);
}

/* Content loading overlay */
.detail-loading-overlay {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-6);
  padding: var(--spacing-10);
  color: var(--text-muted);
  font-size: var(--fs-base);
  min-height: 200px;
}

/* Fade transition on hero content */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-row-top,
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-body {
  transition: opacity 0.25s ease;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header)[loading] .hero-row-top,
:is(ui-detail-hero, ui-policy-hero, ui-policy-header)[loading] .hero-body {
  opacity: 0;
  pointer-events: none;
}
/*
  The nav fades on the same curve as the hero, for the same reason.

  ui-detail-view only sets [loading] on the nav when a product or transaction shell
  may replace it, so a view whose authored nav is final never fades. Without this the
  module's generic spine was visible at full opacity for the whole entity fetch and
  was then swapped for the product shell's — a flash of the wrong product's nav.
*/
:is(ui-detail-sidebar, ui-policy-nav) {
  transition: opacity 0.25s ease;
}
:is(ui-detail-sidebar, ui-policy-nav)[loading] {
  opacity: 0;
  pointer-events: none;
}
/* Hide real hero content while skeleton is showing */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-row:not(.hero-skeleton-hidden) ~ .hero-v2 {
  opacity: 0;
  pointer-events: none;
  max-height: 0;
  overflow: hidden;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-5) var(--spacing-8);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  transition: opacity 0.3s ease, max-height 0.3s ease;
  overflow: hidden;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-row.hero-skeleton-hidden {
  opacity: 0;
  max-height: 0;
  padding-top: 0;
  padding-bottom: 0;
  border-bottom: none;
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-subs {
  display: flex;
  gap: var(--spacing-5);
  margin-left: var(--spacing-4);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-sep {
  color: var(--text-dim);
  font-size: var(--fs-sm);
}
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-skeleton-metas {
  display: flex;
  gap: var(--spacing-4);
  margin-left: var(--spacing-4);
}

/* ══════════════════════════════════════════════════════════════════
   CONTENT — ui-key-value, ui-collapsible, ui-timeline
   ══════════════════════════════════════════════════════════════════ */

ui-key-value {
  display: block;
}

ui-key-value .kv-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin-bottom: var(--spacing-2);
}

ui-key-value .kv-value {
  font-size: var(--fs-lg);
  color: var(--text);
}
ui-key-value .kv-badge {
  display: inline-block;
  padding: var(--spacing-1) var(--spacing-4);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--text-muted);
  line-height: var(--lh-normal);
}
ui-key-value .kv-badge-success { background: var(--success-bg); color: var(--success); }
ui-key-value .kv-badge-warning { background: var(--warning-bg); color: var(--warning); }
ui-key-value .kv-badge-danger  { background: var(--danger-bg); color: var(--danger); }
ui-key-value .kv-badge-info    { background: var(--info-bg); color: var(--info); }
ui-key-value .kv-badge-muted   { background: var(--surface-2); color: var(--text-muted); }
/* format="boolean": a green tick for yes, a NEUTRAL mark for no. --text-muted
   and not --danger, because red states that the answer is bad and the SDK has
   no idea which answer is bad — "Suit Filed: no" is the good news (ND-82
   follow-up). --text-muted rather than the dimmer --text-dim: this mark IS the
   value, so it has to stay legible, not recede. */
ui-key-value .kv-bool-pass { color: var(--success); display: inline-flex; }
ui-key-value .kv-bool-fail { color: var(--text-muted); display: inline-flex; }
ui-key-value .kv-code {
  font-family: var(--mono);
  font-size: var(--fs-base);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-2xs);
  background: var(--surface-2);
}

ui-text {
  display: block;
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

ui-text[variant='heading'] {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-text[variant='caption'] {
  font-size: var(--fs-base);
  color: var(--text-muted);
}

ui-text[variant='title'] {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-text[variant='label'] {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-text[variant='muted'] {
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: var(--lh-relaxed);
}

/* ui-text declared a `color` enum of five semantic tones and implemented NONE
   of them — no CSS rule, no JS. An authored color="danger" resolved to the
   inherited body color, so a login error rendered in ordinary text. Third
   attribute found this way (ui-stack max-width, ui-form-field email/tel were
   the others), all by building a real page against the schema.

   After `variant`, so a tone beats a variant's color — which is the point of
   asking for one. */
ui-text[color='danger'] { color: var(--danger); }
ui-text[color='warning'] { color: var(--warning); }
ui-text[color='success'] { color: var(--success); }
ui-text[color='info'] { color: var(--info); }
ui-text[color='muted'] { color: var(--text-muted); }

/* `preserve-whitespace` and `measure` were the same class of defect as the `color`
   enum above: declared in the schema, implemented in neither CSS nor JS, so an
   authored attribute was silently inert. Newlines collapsed, and a plain-text email
   body — which is every email_body record the intake path writes — reached the
   screen as one run-on paragraph.

   break-word is not optional alongside pre-wrap. pre-wrap honors the newlines but
   still refuses to break an unbroken token, so a pasted URL or a signature rule of
   120 dashes widens the container and drags every sibling out with it. */
ui-text[preserve-whitespace] {
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

/* A MEASURE, NOT A WIDTH — the readable line length is a property of the type, so
   it is set in ch and caps the line box only. Matches the 52ch already carried by
   ui-app's route notice, widened for a left-aligned block of body copy rather than
   a centered notice of two sentences. */
ui-text[measure] {
  max-width: 68ch;
}

ui-text[variant='code'] {
  font-family: var(--mono);
  font-size: var(--fs-base);
  background: var(--surface-2);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-xs);
  color: var(--accent);
  display: inline;
}

ui-text[variant='mono'] {
  font-family: var(--mono);
  font-size: var(--fs-base);
  color: var(--success);
  min-height: 18px;
  white-space: pre-wrap;
}

ui-text[variant='mono'][color='danger'] {
  color: var(--danger);
}
ui-text[variant='mono'][color='warning'] {
  color: var(--warning);
}
ui-text[variant='mono'][color='muted'] {
  color: var(--text-muted);
}

/* ── ui-text[clamp] — a band of exactly N lines ─────────────────────
   TWO HALVES, and the second is the one that is easy to leave out. The clamp caps
   the long description; `min-height` RESERVES the space for the short one. Without
   the reserve, a row of cards whose descriptions differ in length is still ragged
   — which is the complaint a description band gets dropped over.

   `1lh` is the element's own computed line-height, so the band tracks whatever
   variant is on the element rather than assuming a size. Enumerated 1–4 rather
   than driven by attr(): typed attr() is too new to rely on.

   SELECTOR-LISTED, NOT `[clamp]`. With the bare attribute-presence selector, an
   out-of-enum clamp="5" still became a -webkit-box with overflow:hidden and no
   line-clamp — so it CLIPPED at the element's height with no ellipsis and no
   warning, which is worse than doing nothing. Listing the four supported values
   makes an unsupported one completely inert: ordinary text, visibly unclamped.
   Found by browser-verifier. */
ui-text[clamp='1'],
ui-text[clamp='2'],
ui-text[clamp='3'],
ui-text[clamp='4'] {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}
ui-text[clamp='1'] { -webkit-line-clamp: 1; min-height: 1lh; }
ui-text[clamp='2'] { -webkit-line-clamp: 2; min-height: 2lh; }
ui-text[clamp='3'] { -webkit-line-clamp: 3; min-height: 3lh; }
ui-text[clamp='4'] { -webkit-line-clamp: 4; min-height: 4lh; }

ui-collapsible {
  display: block;
  margin-bottom: var(--spacing-6);
}

ui-collapsible .collapsible-header {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
  cursor: pointer;
  padding: var(--spacing-4) 0;
  user-select: none;
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}

ui-collapsible .collapsible-badge {
  margin-left: auto;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  padding: var(--spacing-1) var(--spacing-4);
  border-radius: var(--radius-md);
  line-height: var(--lh-none);
}
ui-collapsible .collapsible-badge.badge-success { color: var(--success); background: rgba(34, 197, 94, 0.1); }
ui-collapsible .collapsible-badge.badge-danger { color: var(--danger); background: rgba(239, 68, 68, 0.1); }
ui-collapsible .collapsible-badge.badge-warning { color: var(--warning); background: rgba(245, 158, 11, 0.1); }
ui-collapsible .collapsible-badge.badge-info { color: var(--accent); background: rgba(59, 130, 246, 0.1); }
ui-collapsible .collapsible-badge.badge-muted { color: var(--text-muted); background: var(--surface-2); }

ui-collapsible .collapsible-arrow {
  font-size: var(--fs-base);
  margin-right: var(--spacing-2);
}

ui-collapsible .collapsible-body {
  padding: var(--spacing-4) 0 var(--spacing-4) var(--spacing-7);
}

/* ── ui-callout — inline status banner ─────────────────────────────
   Five tones over the EXISTING semantic tokens. No new color values: the
   border is derived from the same token with color-mix, which is the pattern
   .hero-banner-* already uses, so the two banners cannot drift apart.

   `muted` is the one that has no --muted-bg token, because none exists — it
   uses --surface-2 over --text-muted rather than introducing one, since a
   neutral callout wants the ordinary raised-surface color, not a tinted one.

   The heading carries the tone; the detail line stays --text-muted. A tone
   color reads well on one short line (hero-banner is single-line) but fights
   legibility over a wrapped sentence, and the detail line here is prose. */

ui-callout {
  display: block;
}

ui-callout .callout {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-5);
  padding: var(--spacing-6) var(--spacing-7);
  border-radius: var(--radius-sm);
}

ui-callout .callout-icon {
  display: inline-flex;
  flex-shrink: 0;
  /* Optical alignment with the heading's cap height rather than its line box. */
  margin-top: var(--spacing-1);
}

ui-callout .callout-body {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  /* min-width:0 is what actually permits the wrap: a flex item's default
     min-width:auto refuses to shrink below its longest word, so a long detail
     sentence would widen the callout instead of wrapping inside it. */
  min-width: 0;
}

ui-callout .callout-heading {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
}

ui-callout .callout-detail {
  font-size: var(--fs-base);
  font-weight: var(--fw-normal);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

/*
 * The optional action link. `currentColor` deliberately: the callout body already carries the
 * variant's color, so the link reads as part of the statement rather than as a control
 * pasted onto it, and it stays legible on all five tinted grounds without five rules.
 *
 * A <button>, not an <a>: it navigates the SPA router, and an anchor would need an href that
 * either lies about being a document link or is "#", which is a real navigation on middle
 * click. align-self keeps it to its own text width instead of the full callout.
 */
ui-callout .callout-action {
  align-self: flex-start;
  margin-top: var(--spacing-1);
  padding: 0;
  border: 0;
  background: none;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  color: currentColor;
  text-decoration: underline;
  cursor: pointer;
}

ui-callout .callout-action:hover {
  text-decoration: none;
}

/* Adjacent gap, fixed inline: this button had no SDK focus ring at all and fell through
   to Chrome's UA default, a blue that is off-palette on all five tinted grounds. Same
   two declarations .uib and ui-card[clickable] already use, so the ring is the one the
   rest of the SDK draws. Pre-dates action-tab — the route flavor had it too. */
ui-callout .callout-action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

ui-callout .callout-success {
  background: var(--success-bg);
  color: var(--success);
  border: 1px solid color-mix(in srgb, var(--success) 25%, transparent);
}
ui-callout .callout-warning {
  background: var(--warning-bg);
  color: var(--warning);
  border: 1px solid color-mix(in srgb, var(--warning) 25%, transparent);
}
ui-callout .callout-danger {
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid color-mix(in srgb, var(--danger) 25%, transparent);
}
ui-callout .callout-info {
  background: var(--info-bg);
  color: var(--info);
  border: 1px solid color-mix(in srgb, var(--info) 25%, transparent);
}
ui-callout .callout-muted {
  background: var(--surface-2);
  color: var(--text-muted);
  border: 1px solid var(--border);
}

/* ── ui-requirement-list — prose rows with a state glyph ───────────
   SIZED FOR PROSE, NOT FOR THE SEEDED FIXTURES. Real remediation sentences
   reach ~96 characters while the longest name in the sample data is 33, so
   anything laid out to the sample would wrap badly the first time it met real
   content. Hence: no fixed row height, no truncation, no ellipsis, --lh-relaxed
   on both sentences, and min-width:0 on the flex body so a long sentence wraps
   inside the row instead of widening it.

   The three tones come from the existing semantic tokens. Only the glyph is
   tinted: tinting a whole three-line row in --danger would make a list of
   outstanding items unreadable, and the glyph already carries the state. */

ui-requirement-list {
  display: block;
}

ui-requirement-list .rql-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
}

ui-requirement-list .rql-row {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-5);
}

ui-requirement-list .rql-glyph {
  display: inline-flex;
  flex-shrink: 0;
  /* Aligns the glyph to the label's cap height rather than its line box. */
  margin-top: var(--spacing-1);
}

ui-requirement-list .rql-met .rql-glyph { color: var(--success); }
ui-requirement-list .rql-outstanding .rql-glyph { color: var(--danger); }
ui-requirement-list .rql-waived .rql-glyph { color: var(--text-dim); }

ui-requirement-list .rql-body {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  min-width: 0;
  flex: 1;
}

ui-requirement-list .rql-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-5);
}

ui-requirement-list .rql-label {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  color: var(--text);
}

/* The trailing chip. flex-shrink:0 keeps it intact while the label wraps, which
   is the right priority: the target is a short identifier and the name is not. */
ui-requirement-list .rql-panel {
  flex-shrink: 0;
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-full);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

ui-requirement-list .rql-detail {
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

/* The action line is the one a user acts on, so it is the one that stays at full
   text color while the current-state sentence recedes. */
ui-requirement-list .rql-remediation {
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

ui-requirement-list .rql-waived .rql-label {
  color: var(--text-muted);
}

/* ── compact rows: one line, and the rest behind a disclosure (ND-901) ────────
   The three-line row was three paraphrases of one fact. Here the producer's single
   imperative line leads, and the name, the current state and the remediation are one
   click away — ranked, not deleted. */
ui-requirement-list .rql-row.rql-compact {
  display: block;
  padding: var(--spacing-4) 0;
  border-top: 1px solid var(--border);
}

/* No rule above the first row: the callout heading is already its top edge, and a
   second line there reads as an empty first row. */
ui-requirement-list .rql-row.rql-compact:first-child {
  border-top: 0;
  padding-top: 0;
}

ui-requirement-list .rql-line {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-5);
}

/* min-width:0 so a long instruction wraps instead of forcing the row wider than its
   container and pushing the fix control out of view. The control never shrinks; the
   text is what yields, which is the right priority when the control is the affordance. */
ui-requirement-list .rql-todo {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  color: var(--text);
}

/* ── the fix control ─────────────────────────────────────────────────────────
   A <button> in both flavors, because both DO something on activation and a button is
   what a keyboard and a screen reader already understand. The navigating flavor carries
   an arrow; the acting flavor does not, and that is the only signal distinguishing
   "this moves you" from "this performs something". */
ui-requirement-list .rql-fix {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font: inherit;
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  color: var(--accent);
  cursor: pointer;
}

ui-requirement-list .rql-fix:hover {
  border-color: var(--accent);
}

ui-requirement-list .rql-fix-act {
  color: var(--success);
}

ui-requirement-list .rql-fix-act:hover {
  border-color: var(--success);
}

ui-requirement-list .rql-fix-arrow {
  color: var(--text-muted);
}

/* ── the per-row disclosure ───────────────────────────────────────────────────
   THE ROW STAYS ONE LINE. The <summary> IS the line, so the trigger costs no vertical
   space at all — the affordance is the caret below, drawn on the glyph's left, not a
   second line of text under the instruction. */
ui-requirement-list .rql-disclose > summary.rql-line {
  cursor: pointer;
  /* Both are needed: Safari honors only the pseudo-element, Chrome and Firefox only
     the property. Left in place, the default triangle becomes a third flex item. */
  list-style: none;
}

ui-requirement-list .rql-disclose > summary.rql-line::-webkit-details-marker {
  display: none;
}

/* The caret, in the gutter to the left of the glyph, so it never displaces the
   instruction and a row with nothing to reveal is simply missing it. */
ui-requirement-list .rql-disclose > summary.rql-line::before {
  content: '';
  flex: none;
  width: 0;
  height: 0;
  margin-top: var(--spacing-4);
  border-left: 4px solid var(--text-dim);
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
}

ui-requirement-list .rql-disclose[open] > summary.rql-line::before {
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  border-top: 4px solid var(--text-dim);
  border-bottom: 0;
  margin-top: calc(var(--spacing-4) + 2px);
}

/* A row with no disclosure keeps the same text alignment as one that has it, so the
   instructions in a list form a single column. Matches the ::before box plus the gap. */
ui-requirement-list .rql-row.rql-compact > .rql-line {
  padding-left: calc(4px + var(--spacing-5));
}

/* Indented past the caret AND the glyph, so the revealed prose lines up with the
   instruction above it rather than with the marks in the gutter. */
ui-requirement-list .rql-more {
  margin-top: var(--spacing-3);
  margin-left: calc(4px + var(--spacing-5) + var(--spacing-7) + var(--spacing-5));
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

ui-requirement-list .rql-empty {
  margin: 0;
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

/* ── Cards ─────────────────────────────────────────────────────── */

ui-card {
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

ui-card[variant="outlined"] {
  background: transparent;
}

ui-card[variant="elevated"] {
  box-shadow: var(--elev-card);
  border-color: transparent;
}

ui-card[variant="flush"] {
  border: none;
  border-radius: 0;
  background: transparent;
}

/* RECEDES, DOES NOT DISABLE. A card with nothing in it yet should read as quieter
   than its neighbors and still as reachable, so only the contrast drops — the
   border, the hover rule and the focus ring below are all untouched. Dimming the
   CONTENT rather than setting a color on the card keeps whatever tone a badge or a
   status pill inside it already has. */
ui-card[variant="muted"] {
  background: var(--surface-2);
}
ui-card[variant="muted"] ui-card-header,
ui-card[variant="muted"] ui-card-body,
ui-card[variant="muted"] ui-card-footer {
  opacity: 0.72;
}
/* Restored on interaction, so the affordance is never the thing that is dimmed. */
ui-card[variant="muted"]:hover ui-card-header,
ui-card[variant="muted"]:hover ui-card-body,
ui-card[variant="muted"]:hover ui-card-footer,
ui-card[variant="muted"]:focus-within ui-card-header,
ui-card[variant="muted"]:focus-within ui-card-body,
ui-card[variant="muted"]:focus-within ui-card-footer {
  opacity: 1;
}

/* Opt-in, never the default: ui-card allows @flow content, and turning every card
   into a flex column would stop two inline children flowing inline across 261
   authored files. See the schema comment on fill-height. */
ui-card[fill-height] {
  display: flex;
  flex-direction: column;
}
ui-card[fill-height] ui-card-body {
  flex: 1 1 auto;
}
ui-card[fill-height] ui-card-footer {
  margin-top: auto;
}

/* [click-route] carries the same affordance as [clickable] because it IS the same
   affordance — ui-card treats either as "this whole surface is a target" and sets
   tabindex/role for both. Selector-listed rather than folded into `clickable` so a
   navigable card needs one attribute, not two: a click-route card missing the
   pointer/hover/focus-ring reads as an inert panel that happens to navigate. */
ui-card[clickable],
ui-card[click-route] {
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s;
}

ui-card[clickable]:hover,
ui-card[click-route]:hover {
  border-color: var(--accent);
}

ui-card[clickable]:focus-visible,
ui-card[click-route]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

ui-card[selected] {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

ui-card[compact] ui-card-header {
  padding: var(--spacing-3) var(--spacing-5);
}

ui-card[compact] ui-card-body {
  padding: var(--spacing-4) var(--spacing-5);
}

ui-card[compact] ui-card-footer {
  padding: var(--spacing-3) var(--spacing-5);
}

ui-card-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-5) var(--spacing-7);
  border-bottom: 1px solid var(--border);
}

/* Same four values, same meaning, as ui-card-footer[align] below. The header
   lacked them, and because the header is itself a flex container a nested
   ui-stack is a content-sized flex item — so `justify="between"` on a wrapper
   spreads nothing and there was NO way to put a control at the right end of a
   card header from an authoring repo. */
ui-card-header[align="start"] { justify-content: flex-start; }
ui-card-header[align="center"] { justify-content: center; }
ui-card-header[align="end"] { justify-content: flex-end; }
ui-card-header[align="between"] { justify-content: space-between; }

ui-card-header[divider="false"],
ui-card-header:not([divider]) + ui-card-body:empty ~ ui-card-header {
  border-bottom: none;
}

ui-card-body {
  display: block;
  padding: var(--spacing-7);
}

ui-card-body[padding="none"] { padding: 0; }
ui-card-body[padding="sm"] { padding: var(--spacing-4); }
ui-card-body[padding="md"] { padding: var(--spacing-7); }
ui-card-body[padding="lg"] { padding: var(--spacing-9); }

/* A card had ONE background: ui-card sets --surface and the three regions are
   separated only by 1px rules, so a card whose body is content and whose header
   and footer are chrome reads as one flat block. `page` uses the page background
   rather than a fixed light gray, so it contrasts in the dark theme too — there
   --bg is DARKER than --surface, where in the light theme it is lighter. */
ui-card-body[surface="muted"] { background: var(--surface-2); }
ui-card-body[surface="page"] { background: var(--bg); }

/* ui-repeat had NO rule at all, so a repeated block element — a card, a callout —
   stamped flush against its neighbor with nothing between them. The parent
   ui-stack's gap does not help: the repeat is ONE child of that stack, and the
   items are inside it.

   OPT-IN, and it has to be: `display: flex` unconditionally would change the
   layout of every existing ui-repeat in the product, including the ones whose
   items are table rows or timeline entries. The container only becomes a column
   flex when a gap is asked for. Same token scale as ui-stack[gap]. */
ui-repeat[gap] { display: flex; flex-direction: column; }
ui-repeat[gap='none'] { gap: 0; }
ui-repeat[gap='xs'] { gap: var(--spacing-2); }
ui-repeat[gap='sm'] { gap: var(--spacing-4); }
ui-repeat[gap='md'] { gap: var(--spacing-7); }
ui-repeat[gap='lg'] { gap: var(--spacing-8); }
ui-repeat[gap='xl'] { gap: var(--spacing-9); }

ui-card-footer {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-5) var(--spacing-7);
  border-top: 1px solid var(--border);
  justify-content: flex-end;
}

ui-card-footer[align="start"] { justify-content: flex-start; }
ui-card-footer[align="center"] { justify-content: center; }
ui-card-footer[align="end"] { justify-content: flex-end; }
ui-card-footer[align="between"] { justify-content: space-between; }

ui-timeline {
  display: block;
  padding: var(--spacing-4) 0;
}

ui-timeline-item {
  display: flex;
  gap: var(--spacing-6);
  padding: var(--spacing-6) 0;
  border-left: 2px solid var(--border);
  margin-left: var(--spacing-4);
  padding-left: var(--spacing-7);
  position: relative;
}

ui-timeline-item .timeline-dot {
  position: absolute;
  left: -9px;
  top: 14px;
  font-size: var(--fs-base);
}

ui-timeline-item .timeline-content {
  flex: 1;
}

ui-timeline-item .timeline-time {
  font-size: var(--fs-base);
  color: var(--text-muted);
  margin-top: var(--spacing-2);
}

/* ══════════════════════════════════════════════════════════════════
   DETAIL — ui-attachments (v2 — file listing with upload & actions)
   ══════════════════════════════════════════════════════════════════ */

/* NO NEGATIVE MARGIN AND NO WIDTH OVERRIDE: an element cannot know its parent's
   padding, so any hardcoded bleed is a guess about a container it does not own.
   This carried `margin: -20px -24px 0; width: calc(100% + 48px)` to sit flush
   inside `ui-content-panel`'s padding — but `ui-content-panel` is never this
   element's parent in the authored corpus. Every `<ui-attachments>` is wrapped in
   `<ui-stack padding="md">`, which is `--spacing-7` (16px), not 24px. The 8px
   difference per side made the element WIDER than its own container, and the
   overflow propagated up through `ui-stack` to `ui-tab-panel` as a stray
   horizontal scrollbar on the whole tab (measured on the claims Documents panel:
   element 1028px inside a 1012px stack whose content box is 980px; stack and
   panel both scrollWidth 1020 vs clientWidth 1012).

   Full bleed is the container's decision, not the component's. With the bleed
   gone the element sizes to its parent's content box in any container, and a
   caller that wants it edge-to-edge authors `padding="none"` on the wrapper.

   `min-height: 100%` stays and keeps the fill-the-panel intent when there are few
   files while letting the element grow to its content when there are many. The
   double-counting this comment used to describe — the parent sizing its content
   box from the natural height MINUS the -20px, then a percentage `height`
   resolving against that already-reduced figure, which `.att-file-area` absorbed
   as a clipped last row — cannot recur now that the negative margin is gone. */
ui-attachments {
  display: block;
  position: relative;
  min-height: 100%;
}

/* Also `min-height`: the host's height is now `auto`, so a percentage `height`
   here would resolve to auto and stop stretching when there are few files. */
ui-attachments .att-container {
  display: flex;
  flex-direction: column;
  min-height: 100%;
}

/* ── Toolbar ── */
ui-attachments .att-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-4) var(--spacing-7);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

ui-attachments .att-toolbar-left {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}

ui-attachments .att-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-attachments .att-count {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  background: var(--surface-2);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-md);
}

ui-attachments .att-count:empty {
  display: none;
}

ui-attachments .att-toolbar-right {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}

/* Search */
ui-attachments .att-search {
  position: relative;
  display: flex;
  align-items: center;
}

ui-attachments .att-search-icon {
  position: absolute;
  left: 8px;
  display: flex;
  color: var(--text-muted);
  pointer-events: none;
}

ui-attachments .att-search-input {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--spacing-3) var(--spacing-5) var(--spacing-3) var(--spacing-8);
  font-size: var(--fs-base);
  color: var(--text);
  width: 160px;
  outline: none;
  transition:
    border-color 0.15s,
    width 0.2s;
}

ui-attachments .att-search-input:focus {
  border-color: var(--accent);
  width: 200px;
}

/* View toggle */
ui-attachments .att-view-toggle {
  display: flex;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

ui-attachments .att-view-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 26px;
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s;
}

ui-attachments .att-view-btn:hover {
  background: var(--surface-2);
}
ui-attachments .att-view-btn.active {
  background: var(--surface-2);
  color: var(--accent);
}

/* Upload button */
ui-attachments .att-upload-btn {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  background: var(--accent);
  color: var(--text-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: opacity 0.15s;
}

ui-attachments .att-upload-btn:hover {
  opacity: 0.85;
}

/* ── Upload Panel ── */
ui-attachments .att-upload-panel {
  display: none;
  flex-direction: column;
  gap: var(--spacing-5);
  padding: var(--spacing-6) var(--spacing-7);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

ui-attachments .att-upload-panel.open {
  display: flex;
}

ui-attachments .att-dropzone {
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  padding: var(--spacing-7);
  text-align: center;
  transition:
    border-color 0.15s,
    background 0.15s;
  cursor: pointer;
}

ui-attachments .att-dropzone.drag-active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}

ui-attachments .att-dropzone-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-4);
  color: var(--text-muted);
  font-size: var(--fs-md);
}

ui-attachments .att-browse-label {
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
}

/* Staged files */
ui-attachments .att-staged-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

ui-attachments .att-staged-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-5);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

ui-attachments .att-staged-icon {
  display: flex;
  flex-shrink: 0;
}

ui-attachments .att-staged-name {
  flex: 1;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  padding: var(--spacing-2) var(--spacing-4);
  font-size: var(--fs-base);
  color: var(--text);
  outline: none;
}

ui-attachments .att-staged-name:focus {
  border-color: var(--accent);
}

ui-attachments .att-staged-meta {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
}

ui-attachments .att-staged-remove {
  display: flex;
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: var(--spacing-1);
}

ui-attachments .att-staged-remove:hover {
  color: var(--danger);
}

ui-attachments .att-staged-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--spacing-4);
}

ui-attachments .att-staged-clear {
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  cursor: pointer;
}

ui-attachments .att-staged-upload {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  background: var(--accent);
  color: var(--text-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

ui-attachments .att-staged-upload:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ── Drag Overlay ── */
ui-attachments .att-drag-overlay {
  display: none;
  position: absolute;
  inset: 0;
  z-index: var(--z-drag);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 2px dashed var(--accent);
  border-radius: var(--radius);
  align-items: center;
  justify-content: center;
}

ui-attachments .att-drag-overlay.visible {
  display: flex;
}

ui-attachments .att-drag-overlay-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-4);
  color: var(--accent);
  font-size: var(--fs-lg);
  font-weight: var(--fw-medium);
}

/* ── File Area ── */
ui-attachments .att-file-area {
  flex: 1;
  overflow-y: auto;
  padding: 0;
}

/* ── List View ── */
ui-attachments .att-list-header {
  display: flex;
  align-items: center;
  padding: var(--spacing-3) var(--spacing-7);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  position: sticky;
  top: 0;
  z-index: var(--z-elevated);
}

ui-attachments .att-list-header [data-sort] {
  cursor: pointer;
}

ui-attachments .att-list-header [data-sort]:hover {
  color: var(--text);
}

ui-attachments .att-list-check {
  width: 28px;
  flex-shrink: 0;
}
ui-attachments .att-list-col-name {
  flex: 1;
  min-width: 0;
}
ui-attachments .att-list-col-size {
  width: 70px;
  text-align: right;
}
ui-attachments .att-list-col-date {
  width: 90px;
}
ui-attachments .att-list-col-user {
  width: 100px;
}
ui-attachments .att-list-col-actions {
  width: 60px;
  display: flex;
  justify-content: flex-end;
}

ui-attachments .att-list-row {
  display: flex;
  align-items: center;
  padding: var(--spacing-4) var(--spacing-7);
  font-size: var(--fs-md);
  color: var(--text);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.1s;
}

ui-attachments .att-list-row:hover {
  background: var(--surface-2);
}
ui-attachments .att-list-row.selected {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}

ui-attachments .att-list-col {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-attachments .att-file-icon {
  display: flex;
  flex-shrink: 0;
}

ui-attachments .att-file-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-attachments .att-file-ext {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  background: var(--surface-2);
  padding: var(--spacing-1) var(--spacing-2);
  border-radius: var(--radius-2xs);
  flex-shrink: 0;
}

ui-attachments .att-list-col-size,
ui-attachments .att-list-col-date,
ui-attachments .att-list-col-user {
  font-size: var(--fs-base);
  color: var(--text-muted);
}

/* Row actions — always visible (touch-friendly, no hover dependency) */
ui-attachments .att-row-action {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-xs);
  background: transparent;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s;
}
ui-attachments .att-row-action:hover {
  background: var(--surface-2);
  color: var(--accent);
}

/* Checkbox */
ui-attachments .att-checkbox {
  width: 14px;
  height: 14px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* ── Grid View ── */
ui-attachments .att-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--spacing-5);
  padding: var(--spacing-6) var(--spacing-7);
}

ui-attachments .att-grid-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition:
    border-color 0.15s,
    background 0.15s;
}

ui-attachments .att-grid-card:hover {
  border-color: var(--accent);
}
ui-attachments .att-grid-card.selected {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 5%, transparent);
}

ui-attachments .att-grid-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 80px;
  background: var(--surface);
  position: relative;
}

ui-attachments .att-grid-ext {
  position: absolute;
  bottom: 4px;
  right: 6px;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  background: var(--surface-2);
  padding: var(--spacing-1) var(--spacing-2);
  border-radius: var(--radius-2xs);
}

ui-attachments .att-grid-info {
  padding: var(--spacing-4) var(--spacing-5);
  flex: 1;
}

ui-attachments .att-grid-name {
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-attachments .att-grid-meta {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-top: var(--spacing-1);
}

ui-attachments .att-grid-actions {
  position: absolute;
  top: 4px;
  right: 4px;
  display: flex;
  gap: var(--spacing-1);
}

ui-attachments .att-grid-card {
  position: relative;
}

/* ── In-page attachment viewer ──
   Lives in a ui-dialog appended to ui-app, so it is NOT inside ui-attachments
   and cannot be scoped to it. */
.att-preview {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  min-height: 0;
}

.att-preview-truncated {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-4);
  border: 1px solid var(--border);
  border-inline-start: 2px solid var(--warning);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

.att-preview-error {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-2);
  padding: var(--spacing-4);
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

.att-preview-error svg {
  flex: none;
  color: var(--danger);
}

.att-preview-body {
  min-width: 0;
}

.att-preview-pre {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: var(--text);
}

/* ── Floating Action Bar ── */
ui-attachments .att-action-bar {
  display: none;
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--surface-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--spacing-4) var(--spacing-6);
  box-shadow: var(--elev-dropdown);
  align-items: center;
  gap: var(--spacing-5);
  font-size: var(--fs-base);
  z-index: var(--z-sticky);
  white-space: nowrap;
}

ui-attachments .att-action-bar.visible {
  display: flex;
}

ui-attachments .att-bar-count {
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-attachments .att-bar-btn {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  background: var(--accent);
  color: var(--text-on-accent);
  border: none;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: opacity 0.15s;
}

ui-attachments .att-bar-btn:hover {
  opacity: 0.85;
}
ui-attachments .att-bar-btn-danger {
  background: var(--danger);
  /* Stated explicitly rather than inherited. This variant overrides only the
     BACKGROUND, so moving .att-bar-btn to --text-on-accent would have carried a
     near-black brand foreground onto the red fill as a side effect. There is no
     --text-on-danger token, so white is preserved here exactly as it was — the
     contrast of white on --danger is a separate, pre-existing question and is
     deliberately not being changed under an accent fix. */
  color: var(--text-on-status);
}
ui-attachments .att-bar-btn-ghost {
  background: transparent;
  color: var(--text-muted);
  padding: var(--spacing-3);
}

ui-attachments .att-bar-btn-ghost:hover {
  color: var(--text);
}

/* ── Empty State ── */
ui-attachments .att-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-10) var(--spacing-7);
  gap: var(--spacing-6);
}

ui-attachments .att-empty-icon {
  color: var(--text-muted);
  opacity: 0.5;
}

ui-attachments .att-empty-text {
  font-size: var(--fs-md);
  color: var(--text-muted);
}

/* ══════════════════════════════════════════════════════════════════
   CONTENT — ui-embed-frame (static same-origin document in a pane)
   ══════════════════════════════════════════════════════════════════ */

/* The height is authored, so it is set inline on the iframe by the element
   rather than declared here — there is no scale value for "as tall as this
   particular artifact needs to be".

   `flex: 0 0 auto` is the lesson ui-document-viewer learned the hard way: a
   flex child gets `flex-basis: 0%` from a `flex: 1` shorthand on an ancestor
   rule, which overrides a height on anything along the main axis, and the frame
   silently collapses. Stated here so the host cannot be stretched or squashed
   out of the size its iframe asked for. */
ui-embed-frame {
  display: block;
  flex: 0 0 auto;
  width: 100%;
}

ui-embed-frame .efr-frame {
  display: block;
  width: 100%;
  border: none;
  border-radius: var(--radius-sm);
  /* Embedded documents are authored for a page, not for the app's dark surface,
     and an iframe with no background inherits nothing — it paints transparent
     and the app's surface shows through the document's own white space. */
  background: var(--white);
}

/* ══════════════════════════════════════════════════════════════════
   CONTENT — ui-document-viewer (inline document viewer + overlay)
   ══════════════════════════════════════════════════════════════════ */

ui-document-viewer {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

ui-document-viewer .dv-container {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* Header bar */
ui-document-viewer .dv-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-5) var(--spacing-7);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

ui-document-viewer .dv-header-info {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  min-width: 0;
  flex: 1;
}

ui-document-viewer .dv-file-icon {
  display: flex;
  flex-shrink: 0;
}

ui-document-viewer .dv-title-group {
  min-width: 0;
}

ui-document-viewer .dv-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-document-viewer .dv-meta {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  margin-top: var(--spacing-1);
}

ui-document-viewer .dv-meta-sep {
  opacity: 0.4;
}

ui-document-viewer .dv-header-actions {
  display: flex;
  gap: var(--spacing-3);
  flex-shrink: 0;
}

ui-document-viewer .dv-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
}

ui-document-viewer .dv-btn:hover {
  background: var(--surface-2);
}

ui-document-viewer .dv-btn-primary {
  background: var(--accent);
  color: var(--text-on-accent);
  border-color: var(--accent);
}

ui-document-viewer .dv-btn-primary:hover {
  opacity: 0.85;
}

/* Content area */
ui-document-viewer .dv-content {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Center helper (loading, error, fallback) */
ui-document-viewer .dv-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  padding: var(--spacing-9);
  gap: var(--spacing-6);
  text-align: center;
}

ui-document-viewer .dv-error-icon,
ui-document-viewer .dv-empty-icon,
ui-document-viewer .dv-fallback-icon {
  opacity: 0.5;
  color: var(--text-muted);
}

ui-document-viewer .dv-error-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-document-viewer .dv-error-sub {
  font-size: var(--fs-base);
  color: var(--text-muted);
  max-width: 320px;
}

ui-document-viewer .dv-loading-text {
  font-size: var(--fs-base);
  color: var(--text-muted);
  margin-top: var(--spacing-2);
}

/* Spinner */
ui-document-viewer .dv-spinner {
  width: 28px;
  height: 28px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: var(--radius-circle);
  animation: dv-spin 0.7s linear infinite;
}

@keyframes dv-spin {
  to { transform: rotate(360deg); }
}

/* Iframe (PDF + HTML) */
ui-document-viewer .dv-iframe {
  flex: 1;
  border: none;
  min-height: 0;
  width: 100%;
  background: var(--white);
}

/* Image viewer */
ui-document-viewer .dv-image-viewer {
  flex: 1;
  overflow: auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-7);
  background-image: radial-gradient(circle, var(--border) 1px, transparent 1px);
  background-size: 16px 16px;
}

ui-document-viewer .dv-image-viewer img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: var(--radius-xs);
  box-shadow: var(--elev-image);
}

/* Text viewer */
ui-document-viewer .dv-text-viewer {
  flex: 1;
  overflow: auto;
  padding: var(--spacing-7);
  margin: 0;
  font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
  font-size: var(--fs-base);
  line-height: var(--lh-loose);
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text);
  background: var(--surface);
}

/* ── Overlay mode ── */
.dv-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: rgba(0,0,0,0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.dv-overlay.open {
  opacity: 1;
}

.dv-overlay-container {
  position: relative;
  width: 90vw;
  height: 85vh;
  max-width: 1200px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--elev-overlay);
  transform: translateY(12px);
  transition: transform 0.2s ease;
}

.dv-overlay.open .dv-overlay-container {
  transform: translateY(0);
}

.dv-overlay-close {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm);
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

.dv-overlay-close:hover {
  background: var(--surface-2);
  color: var(--text);
}

/* ══════════════════════════════════════════════════════════════════
   FORMS — ui-form-field, ui-form, ui-editable-panel
   ══════════════════════════════════════════════════════════════════ */

/* ─── ui-form-field ─── */
ui-form-field {
  display: block;
  min-width: 0;
}
ui-form-field[full-width] {
  grid-column: 1 / -1;
}
/*
  View-mode suppression for `view-hidden` and `hide-when-empty`.

  A CLASS, not the `[view-hidden]` / `[hide-when-empty]` attributes, and not
  style.display. The attributes state INTENT; only the element knows whether the
  intent applies, because `hide-when-empty` depends on the bound value and both
  depend on the current edit mode. Selecting on the attribute would hide the
  field while editing too, which is the one thing neither may ever do.

  style.display is unavailable here: ui-form.values() treats it as "omit from the
  payload" (ui-form.js:195) and ui-form.js:673 already owns it for show-when.
  See the block comment at the toggle site in ui-form-field._render().
*/
ui-form-field.ff-suppressed {
  display: none;
}

.ff-group {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}
.ff-group-checkbox {
  flex-direction: row;
  align-items: center;
}

.ff-label {
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}
.ff-required {
  color: var(--danger);
  font-weight: var(--fw-bold);
}

.ff-input {
  height: 34px;
  width: 100%;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--surface);
  padding: 0 var(--spacing-5);
  font-family: inherit;
  font-size: var(--fs-md);
  color: var(--text);
  outline: none;
  transition:
    border-color 0.15s,
    box-shadow 0.15s;
  box-sizing: border-box;
}
.ff-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-dim);
}
.ff-input::placeholder {
  color: var(--text-dim);
}
.ff-input:disabled,
.ff-input[readonly] {
  opacity: 0.6;
  cursor: not-allowed;
  background: var(--bg);
}
.ff-input-error {
  border-color: var(--danger) !important;
}
.ff-input-error:focus {
  box-shadow: 0 0 0 2px var(--danger-bg) !important;
}

.ff-textarea {
  height: auto;
  min-height: 80px;
  padding: var(--spacing-4) var(--spacing-5);
  resize: vertical;
  line-height: var(--lh-relaxed);
}

.ff-textarea-expand {
  flex: 1;
  min-height: 200px;
  resize: none;
}

.ff-select {
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--spacing-9);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2394a3b8' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}

/* The numeric unit affordance (ND-533). One wrapper for every numeric type:
   currency fills the prefix slot from the tenant currency symbol, percent fills
   the suffix slot from the locale percent sign, and number/integer fill either
   from the authored prefix/suffix attributes.

   These four rules REPLACE .ff-currency-* and .ff-percent-*, which absolutely
   positioned a one-character symbol over the control and bought room for it with
   a fixed padding-left/padding-right of var(--spacing-8). That is right for '$'
   and wrong for every unit this affordance now has to carry — 'sq ft',
   'per $100', '/unit' — all of which would have overlapped the digits, silently,
   at a length no gate in this repo can measure. So the affix is REAL LAYOUT in a
   flex row, exactly as .ff-range-wrap chose for its separator and for the same
   reason: layout cannot overlap at any length, and needs no character counting.

   Consequence, accepted deliberately (ND-533, and there are no users yet): the
   currency symbol now sits immediately left of the money box rather than inside
   it, and the percent sign immediately right. The affix is no longer
   pointer-events:none because it is no longer over the control, and no longer
   aria-hidden because for a unit like 'sq ft' it is the only place the unit
   appears at all. */
.ff-affix-wrap {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}
.ff-affix-wrap .ff-input {
  flex: 1 1 0;
  min-width: 0;
}
.ff-affix-prefix,
.ff-affix-suffix {
  flex: 0 0 auto;
  font-size: var(--fs-md);
  line-height: var(--lh-tight);
  color: var(--text-muted);
  white-space: nowrap;
}

/* Date range: two calendar-date inputs sharing one field (ND-531).

   The separator here is NOT overlaid on the control — there are two controls and
   the gap between them is real layout, not an affordance floating over one. Since
   ND-533 .ff-affix-* makes the same choice for the numeric unit affordance, which
   used to be the counter-example this comment pointed at. The ends flex 1 1 0 so they stay equal
   whatever the field's width, which matters because a native date input sizes
   itself to its own placeholder text and two unequal boxes read as two unrelated
   fields rather than one term.

   The dash is drawn by CSS content rather than authored into the markup: it is
   punctuation belonging to this layout, and putting it in element source would
   have been a user-visible literal in a file that must not carry them until the
   i18n runtime lands. It is aria-hidden in the markup either way, so a screen
   reader hears two named date inputs and no stray character. */
.ff-range-wrap {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}
.ff-range-wrap .ff-input {
  flex: 1 1 0;
  min-width: 0;
}
.ff-range-sep {
  flex: 0 0 auto;
  color: var(--text-dim);
  font-size: var(--fs-md);
  line-height: var(--lh-tight);
}
.ff-range-sep::before {
  content: '–';
}

/* Look up this value and fill the siblings (ND-761).

   THE ONLY NEW RULES THE FEATURE NEEDED. The button itself reuses .uib /
   .uib-outline / .uib-sm / .uib-disabled / .uib-spinner, which is the SDK's one
   button vocabulary — a bespoke .ff-lookup-* skin would have been a fourth
   near-miss on the same focus ring and the same padding scale, and there are
   already three too many of those.

   REAL LAYOUT IN A FLEX ROW, exactly as .ff-affix-wrap and .ff-range-wrap chose
   and for the same reason: the label is authored (lookup-label) and translated,
   so it is "Look up" in one tenant, "Decode" in the next and a German compound
   in the third. Anything measured in characters or bought with padding is wrong
   at one of those widths, silently, at a length no gate in this repo can see.

   .ff-affix-wrap is listed alongside .ff-input because a currency or percent
   field has already wrapped its control by the time this wraps it again, so the
   child that has to flex is the affix row rather than the box inside it.

   IT WRAPS, AND THE FLOOR IS WHAT MAKES IT WRAP. The first version of these
   rules was `flex: 1 1 0; min-width: 0` on the control against a button that
   never shrinks — and browser verification found what that costs: with
   lookup-label="Fahrzeugidentifizierungsnummer entschlüsseln" in a 348px form
   column the button took 305px and THE INPUT COLLAPSED TO 35px, showing one
   glyph of a 17-character serial. Nothing overlapped and nothing spilled, so
   the row was doing exactly what it was told; `min-width: 0` had removed the
   automatic minimum, so the control absorbed the whole deficit on its own.

   A 12ch floor plus flex-wrap turns that into a line break: when the label is
   too long to sit beside a usable box, the button drops underneath one that is
   full width. Self-healing at every label length and in every translation,
   which is the property this affordance actually needs — the label is authored
   AND translated, so no character count decided here can be right everywhere.
   Measured after the change: "Decode" 71px button / 271px input on one line,
   unchanged; the German compound now 348px input with the button below. */
.ff-lookup-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-3);
}
.ff-lookup-wrap .ff-input,
.ff-lookup-wrap .ff-affix-wrap {
  flex: 1 1 12ch;
  min-width: 12ch;
}
.ff-lookup-btn {
  flex: 0 0 auto;
}

/* What the lookup SAID about the value (ND-763).

   THREE OUTCOMES, THREE APPEARANCES, and the reason this is not simply reusing
   .ff-error is that only one of the three is an error. An infrastructure failure
   — the call rejected, the answer was not an object — paints .ff-error in
   --danger and marks the control aria-invalid. A business verdict does neither:
   the data it came with has already been applied, the value is not refused, and
   painting it in the error color would tell the user their entry was rejected
   when it was accepted. So a bad verdict is --warning and a good one --success,
   in a slot of their own that sits BELOW the error slot.

   NOT COLOR ALONE. WCAG 1.4.1 aside, the practical case is that the two
   messages a real decoder returns are one word apart — "check digit is correct"
   against "check digit does not match" — so a reader scanning the line has only
   the hue to go on. The ::before glyph carries the same distinction without it,
   and is CSS rather than markup because the message itself is set as textContent
   from an RPC answer and must never become a template.

   The tint is deliberately faint (--warning-bg / --success-bg are 8-12% alpha)
   and the accent is a left border: this line appears underneath a control that
   has just filled in three or four neighboring fields, and a saturated banner
   at that moment reads as a failure of the thing that just worked. */
.ff-lookup-status {
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  display: flex;
  align-items: baseline;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-3);
  border-left: 2px solid transparent;
  border-radius: var(--radius-sm);
  /* THIS SLOT RENDERS RPC TEXT, which is why it breaks a word and .ff-error
     does not. A single unbreakable token — and a URL in an error string is the
     ordinary case, not a contrived one — overflows a flex container by whatever
     it is long, and browser verification measured it painting 82px INTO THE
     NEIGHBORING COLUMN of a two-column form. `min-width: 0` alone would only
     move the clipping; `anywhere` is what wraps mid-token, and it is chosen over
     `break-word` because it also lets the intrinsic width shrink, which is what a
     flex item needs.

     .ff-error has the identical characteristic (measured: 79px of overflow on the
     same string) and is deliberately NOT changed here — it is on every field in
     261 authored files, so it is a separate ticket and a separate look. The
     difference that justifies fixing this one now is provenance: every message in
     this slot came out of a method's answer, so it is the one place a 100-character
     identifier can arrive without anybody authoring it. */
  min-width: 0;
  overflow-wrap: anywhere;
}
.ff-lookup-status:empty {
  display: none;
}
.ff-lookup-status::before {
  flex: 0 0 auto;
  font-weight: var(--fw-bold);
}
.ff-lookup-status-ok {
  color: var(--success);
  background: var(--success-bg);
  border-left-color: var(--success);
}
.ff-lookup-status-ok::before {
  content: '✓';
}
.ff-lookup-status-warn {
  color: var(--warning);
  background: var(--warning-bg);
  border-left-color: var(--warning);
}
.ff-lookup-status-warn::before {
  content: '⚠';
}
/* No verdict was declared, so the element has no basis for calling the message
   good news or bad and says so by staying neutral — see lookup-status-path. */
.ff-lookup-status-info {
  color: var(--text-muted);
  background: var(--info-bg);
  border-left-color: var(--info);
}
.ff-lookup-status-info::before {
  content: 'ℹ';
}

/* The grouped numeric box (ND-535).

   These selectors USED to be .ff-input[type='number'], hiding the spinner
   arrows. Nothing renders a number input any more — number, integer, currency
   and percent all emit type="text" with an inputmode, so they can display a
   grouped figure like 12,500,000 that type="number" blanks outright — and the
   old rules could therefore never match again. They are replaced rather than
   left behind: a selector that cannot match is debt that reads as coverage.

   Tabular figures are the point of the ticket rather than decoration. Grouping
   exists so a seven-figure limit can be PROOFREAD, and proportional digits
   defeat that by making the groups different widths in a column of them. */
.ff-numeric {
  font-variant-numeric: tabular-nums;
}

/* File input (type="file"). The control is taller than a text input because the
   picker button lives inside it, and the chosen-file text is ours rather than
   the browser's — the native "No file chosen" cannot be styled or localized. */
.ff-file {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  min-width: 0;
}
.ff-file-input {
  /* 34px, the same as every other .ff-input, so a form grid mixing a file field
     with text and select fields keeps level rows. The picker button is sized to
     fit inside that height rather than being allowed to set it. */
  padding: 0 var(--spacing-4);
  line-height: var(--lh-normal);
  cursor: pointer;
  /* Hides the control's OWN text — Chrome's "No file chosen" / "2 files", which
     cannot be styled, cannot be localized, and would sit next to .ff-file-names
     saying a second, less useful version of the same thing. The picker button
     below sets its own color, so it is unaffected. */
  color: transparent;
}
.ff-file-input::file-selector-button {
  margin-right: var(--spacing-5);
  height: 24px;
  padding: 0 var(--spacing-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-md);
  cursor: pointer;
}
.ff-file-input::file-selector-button:hover {
  background: var(--surface-3);
}
.ff-file-input:disabled {
  cursor: not-allowed;
}
.ff-file-input:disabled::file-selector-button {
  cursor: not-allowed;
}
.ff-file-names {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ff-file-names:empty {
  display: none;
}

/* Taxpayer identifier (type="tin", ND-528).

   Tabular figures on both the editor and the read-only value: a masked number
   is proofread column by column — "is that group 45 or 46?" — and proportional
   digits make the groups different widths on every render, which is precisely
   the check the mask exists to make possible. Measured, not assumed: 0, 1 and 8
   all advance 8.30px with this set. It does NOT extend to the redaction bullet
   — see .ff-tin-value below.

   No prefix or suffix affordance, so unlike a numeric field it never sits inside
   an .ff-affix-wrap and stays a plain .ff-input. */
.ff-tin-input {
  font-variant-numeric: tabular-nums;
}

/* View mode is a value plus a reveal control, laid out as one line. The gap is
   real layout rather than an overlay: the button is a hit target and must not
   sit on top of the text it uncovers. */
.ff-tin-display {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-3);
  min-width: 0;
}
/* min-width, and this is not cosmetic tidying. tabular-nums fixes the advance
   of FIGURES; U+2022 is not a figure, so it keeps its own narrower width and
   "•••-••-6789" measures 10.9px shorter than "123-45-6789". Without a floor the
   whole line — including the reveal button — jumps sideways on the one
   interaction this control exists for, and the button moves out from under the
   pointer that just clicked it. 11ch is the widest mask (###-##-####) measured
   in the digit advance tabular-nums guarantees, so both masks and both states
   fit and nothing moves. */
.ff-tin-value {
  font-variant-numeric: tabular-nums;
  min-width: 11ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Deliberately quiet — a link-weight control, not a button-weight one. Revealing
   a taxpayer id is a rare, deliberate act on a read-only screen, and giving it
   the visual weight of a primary action would make it the loudest thing in a
   form full of them. Color comes from the accent token so it reads as
   interactive without a border. */
.ff-tin-reveal {
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font-family: inherit;
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  cursor: pointer;
}
.ff-tin-reveal:hover {
  text-decoration: underline;
}
/* Same ring as .uib and the hero controls — 2px accent, offset 2px — so the
   SDK has one focus ring rather than a family of near-misses. */
.ff-tin-reveal:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Postal code (type="postal-code", ND-536).

   Tabular figures for the same reason as .ff-tin-input above: a postal code is
   proofread character by character against a letter or an envelope, and a
   proportional 1 next to a proportional 8 makes two codes of the same length
   render at different widths. It matters more here than on a taxpayer id
   because the field re-formats between countries — '12345-6789' and 'K1A 0B1'
   occupy the same box.

   Letters are upper-cased by the NORMALIZER and not by text-transform. That is
   deliberate and it is the whole difference between a working field and a
   plausible one: text-transform changes only what is painted, so the box would
   show 'K1A 0B1' while .value carried 'k1a 0b1' and the backend received a
   different string from the one the user believes they typed. Presentation
   must not be load-bearing for a value.

   No prefix or suffix affordance, so like .ff-tin-input this stays a plain
   .ff-input and reserves no extra padding. */
.ff-postal-input {
  font-variant-numeric: tabular-nums;
}

/* Telephone number (type="phone" / type="tel", ND-527).

   Tabular figures for the third time, and here the case is the strongest of the
   three: a phone number is read aloud and checked digit by digit against a
   business card or a claim note, it is the longest run of digits any field in
   this element holds, and the mask re-groups it as the country changes —
   '(617) 555-0142' and '01 23 45 67 89' have to occupy the same box without the
   groups appearing to shift.

   No prefix or suffix affordance and no country selector chip, so like
   .ff-tin-input and .ff-postal-input this stays a plain .ff-input and reserves
   no extra padding. The country comes from an attribute or the tenant locale,
   never from a control the user has to find — see _phoneCountry. */
.ff-phone-input {
  font-variant-numeric: tabular-nums;
}

.ff-hint {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  line-height: var(--lh-normal);
}
.ff-error {
  font-size: var(--fs-sm);
  color: var(--danger);
  line-height: var(--lh-normal);
  min-height: 0;
}
.ff-error:empty {
  display: none;
}

/* Toggle (checkbox) */
.ff-toggle {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  cursor: pointer;
  user-select: none;
  padding: var(--spacing-2) 0;
}
.ff-toggle-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.ff-toggle-track {
  position: relative;
  width: 36px;
  height: 20px;
  background: var(--border);
  border-radius: var(--radius-md);
  transition: background 0.15s;
  flex-shrink: 0;
}
.ff-toggle-track::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 14px;
  height: 14px;
  /* UNCHECKED knob only: the track here is --border, so white is correct and
     --text-on-accent would be wrong. The checked knob is re-colored below. */
  background: var(--white);
  border-radius: var(--radius-circle);
  transition: transform 0.15s;
  box-shadow: var(--elev-raised);
}
.ff-toggle-input:checked + .ff-toggle-track {
  background: var(--accent);
}
.ff-toggle-input:checked + .ff-toggle-track::after {
  transform: translateX(16px);
  /* The knob is a foreground drawn ON the accent track once checked, so it obeys
     --text-on-accent like any other on-accent mark. A white knob on trident's
     lime or greenscreen's phosphor is a 1.2–1.7:1 pill with no visible knob, so
     the control reads as "off" in exactly the state it is on. This state carries
     no `color` declaration, so no grep for a hardcoded foreground finds it. */
  background: var(--text-on-accent);
}
.ff-toggle-text {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

/* Radio group (ND-529).

   The <input> is visually hidden and the dot is drawn on the sibling
   .ff-radio-mark, the same technique .ff-toggle above uses and for the same
   reason: a native radio cannot be restyled consistently across platforms, but
   hiding it would throw away real focus, real checked state and the browser's
   own arrow-key handling. It stays in the accessibility tree and keeps the tab
   stop; only its pixels are replaced. Every ring, tint and state below is
   therefore driven off :checked / :focus-visible / :disabled ON THE INPUT. */
.ff-radio-group {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}
/* Inline wraps rather than overflowing: a form field is one column of the
   parent grid, and a row of long option labels has to go somewhere. */
.ff-radio-stacked {
  flex-direction: column;
}
.ff-radio-inline {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--spacing-2) var(--spacing-6);
}
.ff-radio {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  cursor: pointer;
  user-select: none;
  padding: var(--spacing-1) 0;
}
.ff-radio-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.ff-radio-mark {
  position: relative;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-circle);
  background: var(--surface);
  box-sizing: border-box;
  transition:
    border-color 0.15s,
    box-shadow 0.15s;
}
.ff-radio-mark::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-circle);
  background: var(--accent);
  transform: scale(0);
  transition: transform 0.15s;
}
.ff-radio-input:checked + .ff-radio-mark {
  border-color: var(--accent);
}
.ff-radio-input:checked + .ff-radio-mark::after {
  transform: scale(1);
}
/* Same ring as .ff-tin-reveal and the hero controls — 2px accent, offset 2px —
   so the SDK has one focus ring rather than a family of near-misses. It is
   drawn on the mark because the input itself is zero-sized. */
.ff-radio-input:focus-visible + .ff-radio-mark {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.ff-radio-input:disabled + .ff-radio-mark {
  opacity: 0.6;
}
.ff-radio-input:disabled ~ .ff-radio-text {
  opacity: 0.6;
}
.ff-radio:has(.ff-radio-input:disabled) {
  cursor: not-allowed;
}
.ff-radio-text {
  font-size: var(--fs-md);
  color: var(--text);
}
/* The invalid state marks the GROUP, so it tints every option's mark rather
   than accusing the first one. Matches .ff-input-error's use of --danger. */
.ff-radio-group-error .ff-radio-mark {
  border-color: var(--danger);
}

/* Multi-select (type="multiselect", ND-539).

   Three parts stacked in one bordered group: the chips summarizing what is
   chosen, an optional filter box, and the option list.

   The checkbox is visually hidden and the tick is drawn on the sibling
   .ff-ms-mark — the same technique .ff-toggle and .ff-radio above use, and for
   the same reason: a native checkbox cannot be restyled consistently across
   platforms, but hiding it outright would throw away real focus, real checked
   state and the browser's own Space handling. It stays in the accessibility
   tree and keeps its tab stop; only its pixels are replaced. Every ring, tint
   and state below is therefore driven off :checked / :focus-visible /
   :disabled ON THE INPUT.

   The one shape difference from the radio: a square with a tick rather than a
   circle with a dot. That is not decoration — it is the only thing telling a
   user, before they click anything, whether this question takes one answer or
   several. */
.ff-multiselect {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
}
.ff-ms-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-2);
}
/* An empty chip row in the EDITOR collapses entirely rather than reserving
   space; in view mode it holds the em dash, so it is not empty and stays. */
.ff-ms-chips:empty {
  display: none;
}
.ff-ms-none {
  font-size: var(--fs-md);
  color: var(--text-dim);
}
.ff-ms-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-1) var(--spacing-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  font-size: var(--fs-sm);
  color: var(--text);
  max-width: 100%;
}
.ff-ms-chip-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* A real <button>, so Enter and Space already activate it and it is already in
   the tab order — see _renderMultiselect for why interactive() must NOT be
   layered on top of that. Sized to the 24px minimum a pointer target wants
   while the chip itself stays compact. */
.ff-ms-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: var(--fs-lg);
  line-height: var(--lh-none);
  border-radius: var(--radius-circle);
  cursor: pointer;
}
.ff-ms-chip-remove:hover {
  background: var(--border);
  color: var(--text);
}
/* Same ring as .ff-tin-reveal, .ff-radio-mark and the hero controls — 2px
   accent, offset 2px — so the SDK has one focus ring rather than a family of
   near-misses. */
.ff-ms-chip-remove:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* The list scrolls rather than growing without bound: a jurisdiction or
   coverage list runs to dozens of options, and a field that pushes the rest of
   the form off the screen is worse than one that scrolls. */
.ff-ms-options {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  max-height: 180px;
  overflow-y: auto;
  padding: var(--spacing-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.ff-ms-options.ff-ms-options-none {
  display: none;
}
.ff-ms-option {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  cursor: pointer;
  user-select: none;
  padding: var(--spacing-1) 0;
}
.ff-ms-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}
.ff-ms-mark {
  position: relative;
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-2xs);
  background: var(--surface);
  box-sizing: border-box;
  transition:
    border-color 0.15s,
    background 0.15s;
}
/* The tick is two borders of a rotated box — no glyph, so it cannot be
   substituted by a font that lacks it and it needs no icon fetch. */
.ff-ms-mark::after {
  content: '';
  position: absolute;
  top: 1px;
  left: 5px;
  width: 4px;
  height: 8px;
  border: solid var(--text-on-accent);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
  transition: transform 0.15s;
}
.ff-ms-input:checked + .ff-ms-mark {
  border-color: var(--accent);
  background: var(--accent);
}
.ff-ms-input:checked + .ff-ms-mark::after {
  transform: rotate(45deg) scale(1);
}
.ff-ms-input:focus-visible + .ff-ms-mark {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.ff-ms-input:disabled + .ff-ms-mark {
  opacity: 0.6;
}
.ff-ms-input:disabled ~ .ff-ms-text {
  opacity: 0.6;
}
.ff-ms-option:has(.ff-ms-input:disabled) {
  cursor: not-allowed;
}
.ff-ms-text {
  font-size: var(--fs-md);
  color: var(--text);
}
/* The two states a user can act on: nothing to choose from, and nothing
   matching the filter. Muted, because neither is a fault. */
.ff-ms-empty {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  padding: var(--spacing-3);
}
/* The third state IS a fault — the options could not be loaded — so it is
   colored like every other error in this element rather than like an empty
   list. That difference is the whole point of the state existing.

   NOT .ff-ms-* any more (ND-836). type="select" and type="radio" render the
   same state from the same _renderOptionsFailure(), so a multiselect-prefixed
   name would have meant either a second rule saying the same thing or a select
   wearing a class that lies about which control it belongs to. One state, one
   sentence, one class. */
.ff-options-failed {
  font-size: var(--fs-sm);
  color: var(--danger);
  padding: var(--spacing-3);
}
/* Two of the three containers that can hold it are flex ROWS, and there the
   alert has to claim a line of its own or it lands beside the thing it is
   explaining: .ff-radio-group with layout="inline", and the .ff-lookup-wrap
   that goes around any control carrying a lookup-method. Left at the default
   `flex: 0 1 auto` both squeeze the reason into a sliver beside the last option
   or the button — a fault report that is present and unreadable, which is not
   much better than absent. The third container, .ff-ms-options, is a column and
   needs nothing; the stacked radio group is matched here only because it shares
   a class with the inline one, and a full-width row is what it renders anyway.
   padding-left is dropped because these two already indent to the control. */
.ff-radio-group .ff-options-failed,
.ff-lookup-wrap .ff-options-failed {
  flex: 1 0 100%;
  padding-left: 0;
}
/* The invalid state marks the GROUP, so it tints the list border rather than
   accusing one option. Matches .ff-input-error's use of --danger. */
.ff-multiselect-error .ff-ms-options {
  border-color: var(--danger);
}

/* Display value (view mode) */
.ff-display-value {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--text);
  min-height: 20px;
}

/* A defaulted field shows the default's text at reduced weight so it does not
   read as a stored value; .ff-default-note carries the words that say so. */
.ff-display-value.ff-defaulted,
.ff-ms-chips.ff-defaulted {
  color: var(--text-muted);
  font-weight: var(--fw-normal);
  font-style: italic;
}
.ff-default-note {
  display: inline-block;
  margin-top: var(--spacing-1);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ─── Typeahead ─── */
.ff-ta-wrap {
  position: relative;
}
.ff-ta-input {
  padding-right: var(--spacing-8);
}
.ff-ta-spinner {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: var(--radius-circle);
  animation: ff-ta-spin 0.6s linear infinite;
}
@keyframes ff-ta-spin {
  to { transform: translateY(-50%) rotate(360deg); }
}
/* FIXED, NOT ABSOLUTE, AND top/left/width ARE WRITTEN BY THE ELEMENT.
   An absolutely positioned list is clipped by the nearest scrolling ancestor, and
   .dlg-body is `overflow-y: auto` — so a type-ahead in a two-field dialog dropped
   its results into a box shorter than they were, which both cut the list off AND
   gave the dialog body a scrollbar it had no other reason to have. Measured on the
   link-demand dialog.

   The node still lives inside .ff-ta-wrap rather than being mounted on <body>, so
   --z-menu (the LOCAL band) is still correct and every existing query for
   `.ff-ta-dropdown` inside the field keeps resolving. What that costs is that
   `position: fixed` resolves against the nearest TRANSFORMED ancestor rather than
   the viewport — and two of this stylesheet's own containers are one: .drawer-panel
   carries a permanent `transform: translate(0, 0)` while open, and .dlg-panel is a
   transformed ancestor FOR THE 200ms ITS OPEN ANIMATION RUNS and not afterwards. So
   the element can neither write a viewport rect straight onto these offsets nor
   measure the correction once. _positionTypeaheadDropdown zeroes them, measures
   where that landed and corrects by the residual; _trackTypeaheadDropdown redoes it
   every frame the list is open, because a containing block that appears and
   disappears mid-animation announces neither. Both comments are worth reading
   before changing anything here. NO top/left VALUE HERE IS LOAD-BEARING — they
   exist only so the box has a defined place to sit for the one frame before the
   element positions it.

   margin-top stays the gap and stays a token. The element reads it back off the
   computed style rather than restating 4px in JS, because the residual correction
   would otherwise absorb it and the list would sit flush against the input. */
.ff-ta-dropdown {
  position: fixed;
  top: 0;
  left: 0;
  margin-top: var(--spacing-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--elev-menu);
  max-height: 200px;
  overflow-y: auto;
  z-index: var(--z-menu);
}
.ff-ta-item {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  padding: var(--spacing-4) var(--spacing-5);
  cursor: pointer;
  transition: background 0.1s;
}
.ff-ta-item:hover,
.ff-ta-item-active {
  background: var(--accent-dim);
}
.ff-ta-item-primary {
  font-size: var(--fs-md);
  color: var(--text);
  font-weight: var(--fw-medium);
}
.ff-ta-item-secondary {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.ff-ta-empty {
  padding: var(--spacing-5);
  font-size: var(--fs-base);
  color: var(--text-dim);
  text-align: center;
}
/* Pinned to the foot of the scrolling result list. It is the LAST child of a
   max-height:200px overflow-y:auto box, so with more results than fit (6 rows is
   already enough) it sat below the fold and the only route to creating a record
   was to scroll a list you were searching precisely because the record was not in
   it. Opaque background is load-bearing — rows scroll underneath it. */
.ff-ta-create {
  position: sticky;
  bottom: 0;
  background: var(--surface);
  padding: var(--spacing-4) var(--spacing-5);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--accent);
  cursor: pointer;
  border-top: 1px solid var(--border);
  transition: background 0.1s;
}
.ff-ta-create:hover {
  background: var(--accent-dim);
}
.ff-ta-selected {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  min-height: 34px;
  box-sizing: border-box;
}
.ff-ta-sel-info {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  min-width: 0;
}
.ff-ta-sel-primary {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ff-ta-sel-secondary {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.ff-ta-clear {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-xl);
  cursor: pointer;
  border-radius: var(--radius-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.1s, color 0.1s;
}
.ff-ta-clear:hover {
  background: var(--danger-bg);
  color: var(--danger);
}

/* ─── ui-form ─── */
ui-form,
.ui-form {
  display: grid;
  grid-template-columns: repeat(var(--ff-columns, 2), 1fr);
  gap: var(--spacing-6) var(--spacing-7);
  align-items: start;
}
/* Two forms stacked in one container TOUCH without this: ui-form's own `gap`
   applies between its fields, not between it and a sibling form, so the second
   form's description sits directly under the first form's last hint and reads as
   part of it. Seen on the ui-form conformance demo (ND-740).

   Scoped to the adjacency deliberately, so it adds nothing to the ~200 authored
   pages that hold one form per dialog or panel — the rule cannot fire unless two
   forms are already siblings, which is exactly the case that is wrong today.

   --spacing-8 (24px) and not --spacing-7 (16px): the row gap INSIDE a form is
   already --spacing-6 (12px), and browser-verifier's measurement of the 16px
   version was that two forms 4px further apart than two of their own rows read as
   one continuous block. A separation has to be visibly larger than the rhythm it
   is separating. */
.ui-form + .ui-form {
  margin-top: var(--spacing-8);
}
.ff-description {
  grid-column: 1 / -1;
  font-size: var(--fs-base);
  color: var(--text-muted);
  line-height: var(--lh-relaxed);
  margin-bottom: var(--spacing-2);
}
.ff-actions {
  grid-column: 1 / -1;
  display: flex;
  gap: var(--spacing-4);
  justify-content: flex-end;
  padding-top: var(--spacing-4);
  border-top: 1px solid var(--border);
}
.ff-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s,
    opacity 0.15s;
  border: none;
  font-family: inherit;
}
.ff-btn-submit {
  background: var(--accent);
  color: var(--text-on-accent);
}
.ff-btn-submit:hover {
  opacity: 0.9;
}
.ff-btn-cancel {
  background: transparent;
  color: var(--text-muted);
  border: 1px solid var(--border);
}
.ff-btn-cancel:hover {
  background: var(--surface);
  color: var(--text);
}

/* ─── ui-editable-panel ─── */
ui-editable-panel,
.ep-panel {
  display: grid;
  grid-template-columns: repeat(var(--ep-columns, 2), 1fr);
  gap: var(--spacing-5) var(--spacing-7);
  align-items: start;
}

/*
 * Inline save failure. Spans every column like the title bar, because it must be
 * readable regardless of `columns`, and must appear even when `hide-header` removes
 * the title bar — that combination is precisely the case where a refused save used
 * to produce no feedback at all.
 */
.ep-save-error {
  grid-column: 1 / -1;
  padding: var(--spacing-3) var(--spacing-4);
  border: 1px solid var(--danger);
  border-radius: var(--radius-sm);
  background: var(--danger-bg);
  color: var(--danger);
  font-size: var(--fs-sm);
}

.ep-title-bar {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--surface);
  border-radius: var(--radius-sm);
  margin-bottom: var(--spacing-2);
}
.ep-title-icon {
  color: var(--text-muted);
  flex-shrink: 0;
  display: flex;
}
.ep-title-text {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}
.ep-title-actions {
  margin-left: auto;
  display: flex;
  gap: var(--spacing-3);
  align-items: center;
}
.ep-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  padding: var(--spacing-2) var(--spacing-4);
  transition:
    background 0.1s,
    color 0.1s;
  line-height: var(--lh-none);
  font-family: inherit;
}
.ep-btn-edit {
  background: transparent;
  color: var(--text-muted);
}
.ep-btn-edit:hover {
  background: var(--surface-2);
  color: var(--text);
}
.ep-btn-cancel {
  background: transparent;
  color: var(--text-muted);
}
.ep-btn-cancel:hover {
  background: var(--surface-2);
  color: var(--text);
}
.ep-btn-save {
  background: transparent;
  color: var(--success);
}
.ep-btn-save:hover {
  background: var(--success-bg);
}
.ep-btn-save:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* currentColor for the same reason .dlg-spinner uses it: this ring is injected
   INTO the Save button, whose color is --success here and --accent elsewhere, so
   a hardcoded white is invisible on a light brand accent. */
.ep-spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid color-mix(in srgb, currentColor 25%, transparent);
  border-top-color: currentColor;
  border-radius: var(--radius-circle);
  animation: dlg-spin 0.6s linear infinite;
  vertical-align: middle;
}

/* Edit-mode transition */
ui-editable-panel[data-editing="true"] {
  animation: ep-enter-edit 0.35s ease-out;
}

@keyframes ep-enter-edit {
  0% {
    opacity: 0.6;
    transform: scale(0.985);
    filter: brightness(0.9);
  }
  50% {
    filter: brightness(1.08);
  }
  100% {
    opacity: 1;
    transform: scale(1);
    filter: brightness(1);
  }
}

ui-editable-panel .ff-input,
ui-editable-panel .ff-textarea,
ui-editable-panel .ff-select {
  transition: border-color 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
}

/* Light mode: no overrides needed — uses CSS vars from ui-app[theme="light"] */

/* Responsive: single column on narrow screens */
@media (max-width: 500px) {
  ui-form,
  .ui-form,
  ui-editable-panel,
  .ep-panel {
    grid-template-columns: 1fr !important;
  }
}

/* ══════════════════════════════════════════════════════════════════
   DATA TABLE — ui-data-table (.dt-* prefix)
   ══════════════════════════════════════════════════════════════════ */

ui-data-table {
  display: block;
}

.dt-root {
  display: flex;
  flex-direction: column;
  gap: 0;
  font-size: var(--fs-md);
}

/* ─── Toolbar ─── */
.dt-toolbar {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-5) 0;
}
.dt-toolbar-right {
  margin-left: auto;
  display: flex;
  gap: var(--spacing-3);
}
.dt-search {
  position: relative;
  display: flex;
  align-items: center;
}
.dt-search-icon {
  position: absolute;
  left: 10px;
  color: var(--text-dim);
  display: flex;
  pointer-events: none;
}
.dt-search-input {
  padding: var(--spacing-3) var(--spacing-5) var(--spacing-3) 30px;
  font-size: var(--fs-base);
  font-family: inherit;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  width: 220px;
  outline: none;
  transition: border-color 0.15s;
}
.dt-search-input:focus {
  border-color: var(--accent);
}
.dt-search-input::placeholder {
  color: var(--text-dim);
}
.dt-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  font-family: inherit;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s,
    border-color 0.15s;
}
.dt-btn:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--accent);
}

/* ─── Table Scroll Container ─── */
.dt-scroll {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* ─── Table ─── */
.dt-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: auto;
}

/* ─── Header ─── */
.dt-th {
  padding: var(--spacing-5) var(--spacing-6);
  text-align: left;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  user-select: none;
}
.dt-th-content {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
}
.dt-th-sortable {
  cursor: pointer;
  transition: color 0.15s;
}
.dt-th-sortable:hover {
  color: var(--text);
}
/* Sorting is now reachable by keyboard (interactive('.dt-th-sortable')), so the
   header is a focus target. Inset, because a <th> sits flush against the table
   border and an outset ring would be clipped by .dt-scroll's overflow. Same
   rule and same reasoning as ui-list-view .lv-th-sortable above. (ND-98) */
.dt-th-sortable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* The two SORTED states only. This was `.dt-th[aria-sort]`, an attribute-
   PRESENCE test, which was correct while the attribute appeared solely on the
   sorted column — and became wrong the moment every sortable header started
   declaring aria-sort="none" (ND-98). Presence-matching would accent-highlight
   every sortable header at once. */
.dt-th[aria-sort="ascending"],
.dt-th[aria-sort="descending"] {
  color: var(--accent);
}
/* Both chevrons are always in the markup; the header's aria-sort picks which
   one is shown. Deriving the caret from the announced state rather than
   drawing it separately is what stops the arrow and the screen-reader
   announcement disagreeing — they were two independent writes, and only a full
   render performed either, so a user sort moved neither. (ND-98) */
.dt-sort-icon {
  display: none;
  color: var(--accent);
}
.dt-th[aria-sort="ascending"] .dt-sort-asc,
.dt-th[aria-sort="descending"] .dt-sort-desc {
  display: inline-flex;
}
.dt-th-check {
  width: 36px;
  text-align: center;
}
.dt-th-expand {
  width: 32px;
}
.dt-th-actions {
  width: 1%;
  white-space: nowrap;
}
.dt-align-left {
  text-align: left;
}
.dt-align-center {
  text-align: center;
}
.dt-align-right {
  text-align: right;
}
.dt-nowrap {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 0;
}
.dt-wrap {
  white-space: normal;
  word-break: break-word;
}

/* ─── Rows ─── */
.dt-row {
  transition: background 0.1s;
}
.dt-row:not(:last-child) .dt-td {
  border-bottom: 1px solid var(--border);
}
.dt-row:hover {
  background: var(--surface);
}
.dt-row-clickable {
  cursor: pointer;
}
.dt-row-clickable:hover {
  background: var(--accent-dim);
}
.dt-row-selected {
  background: var(--accent-dim) !important;
}

/* Striped */
.dt-striped .dt-row:nth-child(even) {
  background: var(--surface);
}

/* ─── Row variants (row-variant-key) — ND-824 ───
   A table may mix rows that RECORD something with rows that PROJECT something,
   and the second must never be mistaken for the first. So every variant here
   carries TWO signals: a color AND a left border stripe, with `projected` also
   italic. Color alone fails the case these exist for — grayscale printing, a
   screenshot pasted into a ticket, and the ~8% of male readers with a color
   vision deficiency all reduce hue to nothing while a 3px stripe and an italic
   survive all three.

   The stripe uses border-left rather than an ::before so it does not disturb
   cell padding or the -nowrap/ellipsis measurement in the first column. */
.dt-row-muted .dt-td {
  color: var(--text-muted);
}
.dt-row-projected > .dt-td:first-child {
  border-left: 3px solid var(--text-dim);
}
.dt-row-projected .dt-td {
  color: var(--text-muted);
  font-style: italic;
  background: var(--surface);
}
.dt-row-success > .dt-td:first-child {
  border-left: 3px solid var(--success);
}
.dt-row-success .dt-td {
  background: var(--success-bg);
}
.dt-row-warning > .dt-td:first-child {
  border-left: 3px solid var(--warning);
}
.dt-row-warning .dt-td {
  background: var(--warning-bg);
}
.dt-row-danger > .dt-td:first-child {
  border-left: 3px solid var(--danger);
}
.dt-row-danger .dt-td {
  background: var(--danger-bg);
}
.dt-row-info > .dt-td:first-child {
  border-left: 3px solid var(--info);
}
.dt-row-info .dt-td {
  background: var(--info-bg);
}

/* ─── Group divider rows (group-by) — ND-824 ───
   Full width by construction (the element computes the colspan), because the
   whole point is a STRUCTURAL break in the table rather than another cell
   decoration. It is not hoverable and not clickable: it is not a row of data. */
.dt-group-row {
  background: var(--surface-2);
}
.dt-group-row:hover {
  background: var(--surface-2);
}
.dt-group-cell {
  padding: var(--spacing-3) var(--spacing-6);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
}
.dt-group-label {
  display: inline-block;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  line-height: var(--lh-normal);
}
.dt-compact .dt-group-cell {
  padding: var(--spacing-2) var(--spacing-5);
}

/* Compact */
.dt-compact .dt-th {
  padding: var(--spacing-3) var(--spacing-5);
}
.dt-compact .dt-td {
  padding: var(--spacing-3) var(--spacing-5);
}

/* ─── Cells ─── */
.dt-td {
  padding: var(--spacing-5) var(--spacing-6);
  color: var(--text);
  vertical-align: middle;
}
.dt-td-check {
  width: 36px;
  text-align: center;
}
.dt-td-expand {
  width: 32px;
  text-align: center;
}
.dt-td-actions {
  white-space: nowrap;
  text-align: right;
}

/* Checkboxes */
.dt-select-all,
.dt-row-check {
  width: 14px;
  height: 14px;
  accent-color: var(--accent);
  cursor: pointer;
}

/* ─── Expand Button ─── */
.dt-expand-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition:
    transform 0.15s,
    color 0.15s;
}
.dt-expand-btn:hover {
  color: var(--text);
  background: var(--surface-2);
}
.dt-expand-btn.dt-expanded {
  transform: rotate(90deg);
  color: var(--accent);
}


/* ─── Expand Row ─── */
.dt-expand-row .dt-expand-content {
  padding: 0;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
.dt-expand-inner {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: var(--spacing-4) var(--spacing-7);
  padding: var(--spacing-6) var(--spacing-7);
}
.dt-expand-field {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}
.dt-expand-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.3px;
}
.dt-expand-value {
  font-size: var(--fs-md);
  color: var(--text);
}

/* ─── Row Detail (info icon + expandable detail panel) ─── */
.dt-info-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s;
}
.dt-info-btn:hover {
  color: var(--accent);
  background: var(--surface-2);
}
.dt-info-btn.dt-info-active {
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.dt-detail-row .dt-detail-cell {
  padding: 0;
  border-bottom: 1px solid var(--border);
}
.dt-detail-inner {
  padding: var(--spacing-6) var(--spacing-7) var(--spacing-6) 52px;
  background: var(--surface);
  border-top: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
  animation: dt-detail-slide 0.15s ease-out;
}
.dt-detail-inner .dt-detail-label {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.3px;
  margin-bottom: var(--spacing-1);
}
.dt-detail-inner .dt-detail-value {
  font-size: var(--fs-md);
  color: var(--text);
  margin-bottom: var(--spacing-4);
}
.dt-detail-inner .dt-detail-value:last-child {
  margin-bottom: 0;
}
@keyframes dt-detail-slide {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ─── Row Action Buttons ─── */
.dt-action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition:
    background 0.1s,
    color 0.1s;
}
.dt-action-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
.dt-action-danger:hover {
  background: var(--danger-bg);
  color: var(--danger);
}
.dt-action-primary:hover {
  background: var(--accent-dim);
  color: var(--accent);
}

/* ─── Cell Formats ─── */
.dt-cell-empty {
  color: var(--text-dim);
}

/* MONEY WHOSE CURRENCY THE DATA DID NOT STATE (ND-1108).
 *
 * NOT scoped to .dt-*: this is the one money class shared by every sink that
 * renders HTML — a table cell, a ui-key-value, a hero card — because the fact it
 * marks is the same fact in all of them and a per-element copy would drift into
 * three different treatments of one condition.
 *
 * WHY --warning AND NOT --danger. The figure is CORRECT; only its unit is
 * unknown, so this is "read this carefully", not "this is wrong". Danger red on
 * a legitimate amount would train readers to ignore it, and the amount is still
 * the number the backend sent.
 *
 * THE UNDERLINE IS THE LOAD-BEARING PART, not the color, and this is the whole
 * reason the class exists rather than leaving the text to speak for itself. The
 * marker in the string is ISO 4217 XXX, which Chrome renders as the letters
 * "XXX" — honest, but sitting in exactly the position a genuine code sits in on
 * the row above. A dotted underline and a help cursor say "there is something to
 * know here" whatever the engine printed, and the title the sink sets says what.
 * Color alone would also be the only signal for a reader who cannot
 * distinguish it. */
.fmt-unpriced {
  color: var(--warning);
  text-decoration: underline dotted var(--warning);
  text-underline-offset: 2px;
  cursor: help;
}
/* A WORD standing in for an absent value (ui-table-column empty-text). Italic so
   it cannot be misread as the value itself — "unset" in roman is indistinguishable
   from a record whose field literally holds the string "unset". */
.dt-cell-unset {
  font-style: italic;
}
.dt-bold {
  font-weight: var(--fw-semibold);
}
.dt-truncate {
  display: inline-block;
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: middle;
}
/* Secondary line of a compound cell composed by a <template> child of
   <ui-column> or <ui-table-column>. Global, not per-table: the same template
   is authored against all three table elements. */
.cell-sub {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-tight);
  overflow: hidden;
  text-overflow: ellipsis;
}
.dt-code {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  background: var(--surface-2);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-xs);
  color: var(--accent);
}
.dt-code-bold {
  font-weight: var(--fw-bold);
  color: var(--text);
}
.dt-code-plain {
  color: var(--text-muted);
  font-weight: var(--fw-normal);
}

/* Badge */
.dt-badge {
  display: inline-block;
  padding: var(--spacing-1) var(--spacing-4);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  border-radius: var(--radius-md);
  background: var(--surface-2);
  color: var(--text-muted);
  line-height: var(--lh-normal);
}
.dt-badge-success {
  background: var(--success-bg);
  color: var(--success);
}
.dt-badge-warning {
  background: var(--warning-bg);
  color: var(--warning);
}
.dt-badge-danger {
  background: var(--danger-bg);
  color: var(--danger);
}
.dt-badge-info {
  background: var(--info-bg);
  color: var(--info);
}
.dt-badge-muted {
  background: var(--surface-2);
  color: var(--text-muted);
}
/* Neutral false, same reasoning as ui-key-value's .kv-bool-fail (ND-82). */
.dt-bool-pass { color: var(--success); display: inline-flex; }
.dt-bool-fail { color: var(--text-muted); display: inline-flex; }

/* ─── Status Dot ─── */
.dt-status {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
}
.dt-status-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-circle);
  flex-shrink: 0;
}
.dt-status-success { color: var(--success); }
.dt-status-success .dt-status-dot { background: var(--success); box-shadow: 0 0 4px var(--success); }
.dt-status-warning { color: var(--warning); }
.dt-status-warning .dt-status-dot { background: var(--warning); box-shadow: 0 0 4px var(--warning); }
.dt-status-danger { color: var(--danger); }
.dt-status-danger .dt-status-dot { background: var(--danger); box-shadow: 0 0 4px var(--danger); }
.dt-status-info { color: var(--info); }
.dt-status-info .dt-status-dot { background: var(--info); box-shadow: 0 0 4px var(--info); }
.dt-status-muted { color: var(--text-muted); }
.dt-status-muted .dt-status-dot { background: var(--text-muted); }

/* ─── Pagination ─── */
.dt-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-5) 0;
  gap: var(--spacing-6);
}
.dt-page-info {
  font-size: var(--fs-base);
  color: var(--text-muted);
}
.dt-page-btns {
  display: flex;
  align-items: center;
  gap: var(--spacing-1);
}
.dt-page-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 28px;
  padding: 0 var(--spacing-3);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  font-family: inherit;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background 0.1s,
    color 0.1s,
    border-color 0.1s;
}
.dt-page-btn:hover:not(:disabled) {
  background: var(--surface-2);
  color: var(--text);
}
.dt-page-btn:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}
.dt-page-active {
  background: var(--accent-dim);
  color: var(--accent);
  border-color: var(--accent);
}
.dt-page-dots {
  padding: 0 var(--spacing-2);
  color: var(--text-dim);
  font-size: var(--fs-base);
}

/* ─── Selection Bar ─── */
.dt-selection-bar {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--accent-dim);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  margin-top: var(--spacing-4);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--accent);
}
.dt-btn-deselect {
  margin-left: auto;
}

/* ─── Empty State ─── */
.dt-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-10) var(--spacing-8);
  gap: var(--spacing-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}
.dt-empty-icon {
  color: var(--text-dim);
  margin-bottom: var(--spacing-2);
}
.dt-empty-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}
.dt-empty-desc {
  font-size: var(--fs-base);
  color: var(--text-dim);
  max-width: 300px;
  text-align: center;
}

/* ─── Loading State ─── */
.dt-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-5);
  padding: var(--spacing-10) var(--spacing-8);
  color: var(--text-muted);
  font-size: var(--fs-md);
}
.dt-spinner {
  width: 18px;
  height: 18px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: var(--radius-circle);
  animation: dt-spin 0.6s linear infinite;
}
@keyframes dt-spin {
  to {
    transform: rotate(360deg);
  }
}

/* ══════════════════════════════════════════════════════════════════
   TABS — <ui-tabs>, <ui-tab-panels>, <ui-tab-panel>
   ══════════════════════════════════════════════════════════════════ */

ui-tabs {
  display: block;
  flex-shrink: 0;
}
ui-tab-panels {
  display: block;
}
ui-tab-panels[height='fill'] {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
/* The filled tab panel is ITSELF the scroll owner. It used to be `overflow:
   hidden`, which clipped and delegated scrolling to whatever child happened to
   set up as a scroller — and a plain <ui-stack> child never does, because
   ui-stack declares no overflow, no flex and no min-height. This selector's
   specificity (0,1,2) also outranks `ui-content-panel > ui-tab-panels >
   ui-tab-panel { overflow-y: auto }` at line 4814, so it silently revoked the
   scroll that rule was granting. Owning the overflow here is geometry- and
   timing-independent, which is what makes it survive `lazy` re-creating the
   panel's DOM after layout resolved. */
ui-tab-panels[height='fill'] > ui-tab-panel {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
ui-tab-panel {
  display: block;
}
ui-tab-panel[hidden] {
  display: none !important;
}

/* ─── Tab Bar ─── */
.tabs-bar {
  display: flex;
  align-items: center;
  gap: var(--spacing-1);
  border-bottom: 1px solid var(--border);
  padding: 0 var(--spacing-2);
}

.tabs-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-5) var(--spacing-6);
  border: none;
  background: none;
  color: var(--text-muted);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  font-family: inherit;
  cursor: pointer;
  position: relative;
  border-radius: var(--radius) var(--radius) 0 0;
  transition:
    color 0.15s,
    background 0.15s;
  white-space: nowrap;
  user-select: none;
}

.tabs-tab:hover:not([disabled]) {
  color: var(--text);
  background: var(--surface-2);
}

.tabs-tab[aria-selected='true'] {
  color: var(--accent);
  font-weight: var(--fw-semibold);
}

.tabs-tab[aria-selected='true']::after {
  content: '';
  position: absolute;
  bottom: -1px;
  left: 8px;
  right: 8px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px 2px 0 0;
}

.tabs-tab[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.tabs-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ─── Tab Icon ─── */
.tabs-icon {
  display: inline-flex;
  align-items: center;
  line-height: var(--lh-flush);
}
.tabs-icon svg {
  width: 14px;
  height: 14px;
}

/* ─── Tab Badge ─── */
.tabs-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 var(--spacing-3);
  border-radius: var(--radius-full);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  background: var(--surface-3);
  color: var(--text-muted);
}
.tabs-badge-info {
  background: var(--info-bg);
  color: var(--info);
}
.tabs-badge-success {
  background: var(--success-bg);
  color: var(--success);
}
.tabs-badge-warning {
  background: var(--warning-bg);
  color: var(--warning);
}
.tabs-badge-danger {
  background: var(--danger-bg);
  color: var(--danger);
}

/* ─── Variant: pills ─── */
.tabs-pills {
  border-bottom: none;
  gap: var(--spacing-2);
  padding: var(--spacing-2);
  background: var(--surface);
  border-radius: var(--radius);
  border: 1px solid var(--border);
}
.tabs-pills .tabs-tab {
  border-radius: var(--radius-sm);
  padding: var(--spacing-3) var(--spacing-6);
}
.tabs-pills .tabs-tab[aria-selected='true'] {
  background: var(--accent-dim);
  color: var(--accent);
}
.tabs-pills .tabs-tab[aria-selected='true']::after {
  display: none;
}

/* ─── Variant: underline (same as default but no bg hover) ─── */
.tabs-underline .tabs-tab:hover:not([disabled]) {
  background: none;
}

/* ─── Size: sm ─── */
.tabs-sz-sm .tabs-tab {
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  gap: var(--spacing-2);
}
.tabs-sz-sm .tabs-icon svg {
  width: 12px;
  height: 12px;
}
.tabs-sz-sm .tabs-badge {
  min-width: 16px;
  height: 16px;
  font-size: var(--fs-2xs);
}

/* ══════════════════════════════════════════════════════════════════
   ROUTER — <ui-router>, <ui-route>
   ══════════════════════════════════════════════════════════════════ */

/* overflow is VISIBLE at rest, and hidden only while a slide is running.
   It used to be permanently hidden, which clipped every popover a route contains:
   a typeahead dropdown is position:absolute inside the field, so on a short wizard
   step (the submission Parties step is 312px tall) the router's own bottom edge cut
   the result list off mid-row and hid the "+ Create New" row entirely — the control
   was rendered and unreachable, with no scrollbar to reveal it. Clipping is only
   needed for the 30px translateX of the slide keyframes, so it is scoped to exactly
   that: the animation classes are transient, added for the 150-200ms of the
   transition and removed after. At rest a tall step now overflows into ui-module,
   which is overflow:auto and scrolls it. */
ui-router {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: visible;
  position: relative;
}

ui-router:has(> ui-route[class*="rtr-slide"]) {
  overflow: hidden;
}

ui-route {
  display: none;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

ui-route.rtr-active {
  display: flex;
}

ui-route[loading] {
  opacity: 0.6;
  pointer-events: none;
}

/* ─── Slide Transitions ─── */
@keyframes rtr-slide-in-right {
  from { transform: translateX(30px); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}
@keyframes rtr-slide-in-left {
  from { transform: translateX(-30px); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}
@keyframes rtr-slide-out-left {
  from { transform: translateX(0); opacity: 1; }
  to   { transform: translateX(-30px); opacity: 0; }
}
@keyframes rtr-slide-out-right {
  from { transform: translateX(0); opacity: 1; }
  to   { transform: translateX(30px); opacity: 0; }
}

ui-route.rtr-slide-in-right {
  animation: rtr-slide-in-right 200ms ease-out forwards;
}
ui-route.rtr-slide-in-left {
  animation: rtr-slide-in-left 200ms ease-out forwards;
}
ui-route.rtr-slide-out-left {
  animation: rtr-slide-out-left 150ms ease-in forwards;
}
ui-route.rtr-slide-out-right {
  animation: rtr-slide-out-right 150ms ease-in forwards;
}

/* ─── Fade Transitions ─── */
@keyframes rtr-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes rtr-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

ui-route.rtr-fade-in {
  animation: rtr-fade-in 200ms ease-out forwards;
}
ui-route.rtr-fade-out {
  animation: rtr-fade-out 150ms ease-in forwards;
}

/* ══════════════════════════════════════════════════════════════════
   TOAST — <ui-toast> notification system
   ══════════════════════════════════════════════════════════════════ */

ui-toast {
  display: block;
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: var(--z-toast);
  pointer-events: none;
}

.tst-container {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  align-items: flex-end;
}

.tst-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-6) var(--spacing-7);
  min-width: 280px;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--elev-dropdown);
  pointer-events: auto;
  opacity: 0;
  transform: translateX(100%);
  transition: none;
}

.tst-item.tst-show {
  animation: tst-slide-in 0.25s ease-out forwards;
}

.tst-item.tst-hide {
  animation: tst-fade-out 0.2s ease-in forwards;
}

@keyframes tst-slide-in {
  from {
    opacity: 0;
    transform: translateX(100%);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes tst-fade-out {
  from {
    opacity: 1;
    transform: translateX(0);
  }
  to {
    opacity: 0;
    transform: translateX(40px);
  }
}

.tst-success {
  border-left: 3px solid var(--success);
}
.tst-error {
  border-left: 3px solid var(--danger);
}
.tst-warning {
  border-left: 3px solid var(--warning);
}
.tst-info {
  border-left: 3px solid var(--info);
}
.tst-default {
  border-left: 3px solid var(--text-muted);
}

.tst-icon {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  line-height: var(--lh-flush);
}
.tst-success .tst-icon {
  color: var(--success);
}
.tst-error .tst-icon {
  color: var(--danger);
}
.tst-warning .tst-icon {
  color: var(--warning);
}
.tst-info .tst-icon {
  color: var(--info);
}
.tst-default .tst-icon {
  color: var(--text-muted);
}

.tst-message {
  flex: 1;
  font-size: var(--fs-md);
  line-height: var(--lh-normal);
  color: var(--text);
}

.tst-action {
  flex-shrink: 0;
  padding: var(--spacing-2) var(--spacing-5);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--accent);
  background: none;
  border: 1px solid var(--accent);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background 0.15s;
}
.tst-action:hover {
  background: var(--accent-dim);
}

.tst-close {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: none;
  background: none;
  color: var(--text-dim);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition:
    color 0.15s,
    background 0.15s;
}
.tst-close:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* ══════════════════════════════════════════════════════════════════
   TOOLTIP — global tooltip singleton
   ══════════════════════════════════════════════════════════════════ */

ui-tooltip {
  display: inline-block;
}

.tt-tooltip {
  position: fixed;
  z-index: var(--z-tooltip);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--text);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--elev-tooltip);
  pointer-events: none;
  opacity: 0;
  transform: scale(0.95);
  transition:
    opacity 0.15s ease,
    transform 0.15s ease;
  white-space: normal;
  word-wrap: break-word;
  max-width: 240px;
}

.tt-tooltip.tt-visible {
  opacity: 1;
  transform: scale(1);
}

.tt-tooltip.tt-top {
  transform-origin: bottom center;
}
.tt-tooltip.tt-bottom {
  transform-origin: top center;
}
.tt-tooltip.tt-left {
  transform-origin: center right;
}
.tt-tooltip.tt-right {
  transform-origin: center left;
}

.tt-tooltip.tt-visible.tt-top,
.tt-tooltip.tt-visible.tt-bottom,
.tt-tooltip.tt-visible.tt-left,
.tt-tooltip.tt-visible.tt-right {
  transform: scale(1);
}

/* ═══════════════════════════════════════════════════════════════════
   REPEAT (ui-repeat)
   ═══════════════════════════════════════════════════════════════════ */

.repeat-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-10) var(--spacing-8);
  gap: var(--spacing-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}
.repeat-empty-compact {
  padding: var(--spacing-8) var(--spacing-7);
}
.repeat-empty-icon {
  color: var(--text-dim);
  margin-bottom: var(--spacing-2);
}
.repeat-empty-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}
.repeat-empty-desc {
  font-size: var(--fs-base);
  color: var(--text-dim);
  max-width: 300px;
  text-align: center;
}

/* ══════════════════════════════════════════════════════════════════
   PIPELINE LEDGER — <ui-pipeline-ledger>
   ══════════════════════════════════════════════════════════════════ */

ui-pipeline-ledger { display: block; }
ui-pipeline-ledger:empty { display: none; }

.pl-root {
  position: relative;
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--border);
  transition: border-color 0.4s, box-shadow 0.4s;
}
.pl-inner {
  position: relative;
  z-index: var(--z-raised);
  padding: var(--spacing-7) var(--spacing-7);
}

/* Active state: rotating gradient border */
.pl-root.pl-active {
  border-color: transparent;
  background: transparent;
}
.pl-border-wrap { display: none; }
.pl-root.pl-active .pl-border-wrap {
  display: block;
  position: absolute;
  inset: -2px;
  /* Concentric with .pl-root, which is var(--radius-lg): an outset ring needs the
     parent radius PLUS its own inset or the corners visibly mismatch. Was a bare
     14px, which happened to equal 12+2 today and would silently break the moment
     --radius-lg changed. */
  border-radius: calc(var(--radius-lg) + 2px);
  overflow: hidden;
}
.pl-border-wrap .pl-spinner {
  position: absolute;
  inset: -40px;
  background: conic-gradient(
    var(--accent),
    color-mix(in srgb, var(--accent) 60%, transparent),
    color-mix(in srgb, var(--accent) 30%, transparent),
    color-mix(in srgb, var(--accent) 10%, transparent),
    color-mix(in srgb, var(--accent) 30%, transparent),
    color-mix(in srgb, var(--accent) 60%, transparent),
    var(--accent)
  );
  animation: pl-spin 6s linear infinite;
}
.pl-border-wrap .pl-bg {
  position: absolute;
  inset: 2px;
  border-radius: var(--radius-lg);
  background: var(--surface);
  z-index: var(--z-raised);
}
.pl-glow-layer { display: none; }
.pl-root.pl-active .pl-glow-layer {
  display: block;
  position: absolute;
  inset: -4px;
  border-radius: calc(var(--radius-lg) + 4px);  /* concentric, see .pl-border-wrap */
  opacity: 0.5;
  box-shadow: 0 0 15px 2px color-mix(in srgb, var(--accent) 30%, transparent);
  pointer-events: none;
}

/* Finished state */
.pl-root.pl-finished .pl-border-wrap,
.pl-root.pl-finished .pl-glow-layer { display: none; }
.pl-root.pl-finished {
  border-color: var(--border);
  background: var(--surface);
}

/* Heading */
.pl-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
  margin-bottom: var(--spacing-6);
}
.pl-total-duration {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  background: var(--surface-2);
  padding: var(--spacing-1) var(--spacing-4);
  border-radius: var(--radius-full);
}

/* Steps list */
.pl-steps {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}
.pl-step { border-radius: var(--radius); transition: background 0.15s; }
.pl-step.pl-expandable .pl-step-row { cursor: pointer; }
.pl-step.pl-expandable .pl-step-row:hover { background: var(--surface-2); }

.pl-step-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  padding: var(--spacing-4) var(--spacing-5);
  border-radius: var(--radius);
}

.pl-step-dot {
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  border-radius: var(--radius-circle);
  background: var(--border);
  transition: background 0.3s, box-shadow 0.3s;
}

.pl-step-content {
  flex: 1;
  display: flex;
  align-items: baseline;
  gap: var(--spacing-5);
  min-width: 0;
}
.pl-step-name {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  white-space: nowrap;
  transition: color 0.3s;
}
.pl-step-detail {
  font-size: var(--fs-base);
  color: var(--text-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
}
.pl-step-duration {
  flex-shrink: 0;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-dim);
  background: var(--surface-2);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-full);
}
.pl-step-chevron {
  flex-shrink: 0;
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  color: var(--text-dim);
  transition: transform 0.2s ease;
  width: 16px;
  text-align: center;
}
.pl-step-chevron.pl-expanded { transform: rotate(90deg); }

/* Status variants */
.pl-running .pl-step-dot {
  background: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
  animation: pl-pulse 1.5s ease-in-out infinite;
}
.pl-running .pl-step-name { color: var(--text); font-weight: var(--fw-semibold); }
.pl-running .pl-step-detail { color: var(--text-muted); }

.pl-done .pl-step-dot { background: var(--success); box-shadow: 0 0 0 2px color-mix(in srgb, var(--success) 12%, transparent); }
.pl-done .pl-step-name { color: var(--text); }
.pl-done .pl-step-detail { color: var(--text-muted); }

.pl-warn .pl-step-dot { background: var(--warning); box-shadow: 0 0 0 2px color-mix(in srgb, var(--warning) 12%, transparent); }
.pl-warn .pl-step-name { color: var(--text); }
.pl-warn .pl-step-detail { color: var(--warning); }

.pl-fail .pl-step-dot { background: var(--danger); box-shadow: 0 0 0 2px color-mix(in srgb, var(--danger) 12%, transparent); }
.pl-fail .pl-step-name { color: var(--text); }
.pl-fail .pl-step-detail { color: var(--danger); }

/* Evidence panel */
.pl-step-evidence {
  margin: 0 var(--spacing-5) var(--spacing-5) var(--spacing-9);
  padding: var(--spacing-6) var(--spacing-6);
  background: var(--surface-2);
  border-radius: var(--radius);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
}
.pl-ev-section { display: flex; flex-direction: column; gap: 4px; }
.pl-ev-section + .pl-ev-section { padding-top: 10px; border-top: 1px solid var(--border); }
.pl-ev-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin-bottom: var(--spacing-1);
}
.pl-ev-pairs { display: flex; flex-direction: column; gap: 3px; }
.pl-ev-pair { display: flex; align-items: baseline; gap: 10px; font-size: var(--fs-base); }
.pl-ev-key { color: var(--text-muted); min-width: 90px; flex-shrink: 0; }
.pl-ev-val { color: var(--text); font-weight: var(--fw-medium); }
.pl-ev-val-success { color: var(--success); }
.pl-ev-val-warning { color: var(--warning); }
.pl-ev-val-danger { color: var(--danger); }
.pl-ev-val-info { color: var(--accent); }

.pl-ev-list { display: flex; flex-direction: column; gap: 4px; }
.pl-ev-process-item { display: flex; align-items: center; gap: 8px; font-size: var(--fs-base); color: var(--text); }
.pl-ev-process-icon { flex-shrink: 0; display: flex; align-items: center; color: var(--text-muted); }
.pl-ev-process-label { font-weight: var(--fw-medium); }
.pl-ev-process-detail { color: var(--text-muted); }

.pl-ev-reasoning {
  font-size: var(--fs-base);
  color: var(--text);
  line-height: var(--lh-relaxed);
  white-space: pre-wrap;
  word-wrap: break-word;
  background: var(--surface-2);
  padding: var(--spacing-5) var(--spacing-6);
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  max-height: 200px;
  overflow-y: auto;
}

.pl-ev-items-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  cursor: pointer;
  padding: var(--spacing-2) 0;
  font-size: var(--fs-base);
  user-select: none;
}
.pl-ev-items-header:hover .pl-ev-items-label { color: var(--text); }
.pl-ev-items-chevron {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text-dim);
  transition: transform 0.15s ease;
  width: 12px;
}
.pl-ev-items-chevron.pl-open { transform: rotate(90deg); }
.pl-ev-items-label { font-weight: var(--fw-medium); color: var(--text-muted); transition: color 0.15s; }
.pl-ev-items-count {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  color: var(--text-dim);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  padding: 0 var(--spacing-3);
  min-width: 16px;
  text-align: center;
  line-height: var(--lh-relaxed);
}
.pl-ev-items-list { display: flex; flex-direction: column; gap: 2px; padding: 4px 0 4px 18px; }
.pl-ev-items-row {
  font-size: var(--fs-base);
  color: var(--text);
  padding: var(--spacing-2) var(--spacing-4);
  background: var(--surface-2);
  border-radius: var(--radius-xs);
  border: 1px solid var(--border);
}

/* Error / Summary banners */
.pl-error-banner {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  margin-top: var(--spacing-6);
  padding: var(--spacing-5) var(--spacing-6);
  background: color-mix(in srgb, var(--danger) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--danger) 25%, transparent);
  border-radius: var(--radius);
  font-size: var(--fs-md);
  color: var(--danger);
}
.pl-error-icon { display: flex; align-items: center; }
.pl-error-text { flex: 1; }

.pl-summary-banner {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  margin-top: var(--spacing-6);
  padding: var(--spacing-5) var(--spacing-6);
  background: var(--success-bg);
  border: 1px solid color-mix(in srgb, var(--success) 25%, transparent);
  border-radius: var(--radius);
  font-size: var(--fs-md);
  color: var(--success);
}
.pl-summary-icon { display: flex; align-items: center; }

@keyframes pl-pulse {
  0%, 100% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 10%, transparent); }
}
@keyframes pl-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Compact variant */
ui-pipeline-ledger[compact] .pl-inner { padding: 12px 14px; }
ui-pipeline-ledger[compact] .pl-heading { font-size: var(--fs-md); margin-bottom: 10px; }
ui-pipeline-ledger[compact] .pl-steps { gap: 4px; }
ui-pipeline-ledger[compact] .pl-step-name { font-size: var(--fs-base); }
ui-pipeline-ledger[compact] .pl-step-detail { font-size: var(--fs-sm); }

/* ══════════════════════════════════════════════════════════════════
   INLINE LIST — <ui-inline-list> detail panel CRUD list
   ══════════════════════════════════════════════════════════════════ */

ui-inline-list {
  display: block;
  overflow-x: auto;
}

ui-inline-list .il-header {
  display: flex;
  align-items: center;
  padding: var(--spacing-3) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

ui-inline-list .il-hcell {
  flex: 1;
  padding: 0 var(--spacing-4);
  min-width: 110px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-inline-list .il-actions-col {
  flex: 0 0 auto !important;
  margin-left: auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-1);
  padding: 0 var(--spacing-2) 0 var(--spacing-4);
  border-left: 1px solid var(--border);
}

ui-inline-list .il-body {
  display: flex;
  flex-direction: column;
}

ui-inline-list .il-row {
  display: flex;
  align-items: center;
  padding: var(--spacing-4) 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-md);
  color: var(--text);
  transition: background 0.1s;
}

ui-inline-list .il-row:hover {
  background: var(--surface-2);
}

ui-inline-list .il-cell {
  flex: 1;
  padding: 0 var(--spacing-4);
  min-width: 110px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-inline-list .il-primary {
  font-weight: var(--fw-semibold);
}

/* Three distinct outcomes for render="boolean", same reading and same tones as
   .colf-yes/.colf-no and .kv-bool-pass/.kv-bool-fail: yes, no, and NOT ASKED
   (the em-dash). Rendering false as nothing conflated it with an absent key. */
ui-inline-list .il-bool-yes {
  color: var(--success);
}

ui-inline-list .il-bool-no {
  color: var(--text-dim);
}

ui-inline-list .il-cell svg {
  vertical-align: text-bottom;
}

ui-inline-list .il-edit {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  background: none;
  border: none;
  border-radius: var(--radius-xs);
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.15s, color 0.15s;
}

ui-inline-list .il-edit:hover {
  opacity: 1;
  color: var(--accent);
}

ui-inline-list .il-remove {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  background: none;
  border: none;
  border-radius: var(--radius-xs);
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0.5;
  transition: opacity 0.15s, color 0.15s;
}

ui-inline-list .il-remove:hover {
  opacity: 1;
  color: var(--danger);
}

ui-inline-list .il-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-4);
  padding: var(--spacing-9) var(--spacing-7);
  color: var(--text-muted);
  font-size: var(--fs-md);
  text-align: center;
}

ui-inline-list .il-empty svg {
  opacity: 0.4;
}

/* ══════════════════════════════════════════════════════════════════
   CHARTS — <ui-chart-*> declarative chart elements
   ══════════════════════════════════════════════════════════════════ */

/* Chart host elements */
ui-chart-bar,
ui-chart-line,
ui-chart-donut,
ui-chart-gauge,
ui-chart-sparkline,
ui-chart-funnel,
ui-chart-stacked-bar,
ui-chart-heatmap,
ui-chart-scatter,
ui-chart-treemap,
ui-chart-waterfall,
ui-chart-bullet,
ui-chart-progress-ring,
ui-chart-radar,
ui-chart-sankey {
  display: block;
}

/* Chart hosts get the same surface treatment ui-metric-card (--radius-md, small
   tile) and ui-dashboard-table (--radius-lg, large panel) already had, so a chart
   reads as a widget instead of as loose ink on the page background. Charts were
   the ONLY dashboard widget carrying no definition at all — `display: block` and
   nothing else — which is why a chart and the whitespace beside it looked like one
   object. --radius-lg because a chart is a panel, not a tile.

   position: relative is load-bearing, NOT cosmetic. `.cht-legend--right` below is
   `position: absolute; right: 0; top: 30px`, and with no positioned ancestor its
   containing block resolved to BODY — so legend="right" rendered 246px above and
   288px right of its own chart, over the view switcher, with the longest label
   clipped at the viewport edge. Giving the host a containing block fixes that as a
   side effect; it had been filed as an SDK defect and worked around in the Policies
   dashboard by authoring legend="bottom".

   ui-chart-sparkline is excluded deliberately: it is an inline-block meant to sit
   INSIDE a metric card, so a border here would draw a second box around a value. */
ui-chart-bar,
ui-chart-line,
ui-chart-donut,
ui-chart-gauge,
ui-chart-funnel,
ui-chart-stacked-bar,
ui-chart-heatmap,
ui-chart-scatter,
ui-chart-treemap,
ui-chart-waterfall,
ui-chart-bullet,
ui-chart-progress-ring,
ui-chart-radar,
ui-chart-sankey {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--spacing-6);
}

ui-chart-sparkline {
  display: inline-block;
  vertical-align: middle;
}

/* Chart title */
.cht-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  margin-bottom: var(--spacing-4);
}

/* Empty state */
.cht-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 80px;
  color: var(--text-muted);
  font-size: var(--fs-base);
}

/* Fault state — NOT the empty state. A chart handed a payload it cannot plot
   used to draw the same "No data" as a chart with a legitimately empty result,
   and the two must be distinguishable at a glance from across the room, not
   only in a console nobody has open. [ND-1077] */
.cht-fault {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  min-height: 80px;
  padding: var(--spacing-4);
  background: var(--danger-bg);
  border: 1px dashed var(--danger);
  border-radius: var(--radius-md);
  /* --text, NOT --danger. browser-verifier measured --danger on --danger-bg at
     3.98:1 dark / 4.06:1 light — below AA, and LESS legible than the .cht-empty
     message this is supposed to outshout (5.26:1). A warning nobody can read is
     the failure it was written to prevent, one layer up. The alarm is carried by
     the dashed --danger border and the tinted fill, which are decoration and may
     be low-contrast; the sentence is body text and may not. */
  color: var(--text);
  font-size: var(--fs-base);
}

/* Grid lines */
.cht-grid-line {
  stroke: var(--border);
  stroke-width: 0.5;
}

/* Axis labels */
.cht-axis-label {
  font-size: var(--fs-xs);
  fill: var(--text-muted);
  font-family: inherit;
}

.cht-axis-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  fill: var(--text-dim);
  font-family: inherit;
}

/* Tooltip (positioned absolutely on body) */
.cht-tooltip {
  display: none;
  position: fixed;
  z-index: var(--z-tooltip);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  color: var(--text);
  line-height: var(--lh-normal);
  pointer-events: none;
  box-shadow: var(--elev-tooltip);
  max-width: 200px;
}

.cht-tooltip strong {
  color: var(--text);
  font-weight: var(--fw-semibold);
}

/* Legend */
.cht-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-4) var(--spacing-6);
  padding-top: var(--spacing-4);
}

.cht-legend--right {
  flex-direction: column;
  position: absolute;
  right: 0;
  top: 30px;
}

.cht-legend--none { display: none; }

.cht-legend-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.cht-legend-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
}

.cht-legend-swatch--line {
  width: 14px;
  height: 3px;
  border-radius: 1px;
}

.cht-legend-swatch--dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-circle);
}

.cht-legend-label {
  white-space: nowrap;
}

.cht-legend-pct {
  color: var(--text-dim);
  font-size: var(--fs-xs);
}

/* Bar chart */
.cht-bar-rect {
  transition: opacity 0.15s;
}

.cht-bar-rect:hover {
  opacity: 0.8;
}

.cht-bar-value {
  font-size: var(--fs-xs);
  fill: var(--text);
  font-family: inherit;
}

/* Line chart */
.cht-line-path {
  transition: stroke-width 0.15s;
}

.cht-line-dot {
  transition: r 0.15s;
}

.cht-line-dot:hover {
  r: 5;
}

.cht-tooltip-hitbox {
  cursor: default;
}

/* Donut chart */
.cht-donut-area {
  display: flex;
  justify-content: center;
}

.cht-donut-segment {
  transition: opacity 0.15s;
  /* NO cursor: pointer. A chart mark is not clickable — no chart element binds a
     click handler to one; the only thing hovering does is show a tooltip. The
     pointer cursor promised a click that does not exist, and it made every mark a
     mouse-only interactive target under the keyboard-operable gate. That gate had
     been passing vacuously because the conformance charts never drew. [ND-1077] */
}

.cht-donut-segment:hover {
  opacity: 0.8;
}

.cht-donut-center-value {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  fill: var(--text);
  font-family: inherit;
}

.cht-donut-center-label {
  font-size: var(--fs-sm);
  fill: var(--text-muted);
  font-family: inherit;
}

/* Gauge chart */
.cht-gauge-area {
  display: flex;
  justify-content: center;
}

.cht-gauge-value-text {
  font-size: 22px;
  font-weight: var(--fw-bold);
  font-family: inherit;
}

.cht-gauge-label {
  font-size: var(--fs-sm);
  fill: var(--text-muted);
  font-family: inherit;
}

.cht-gauge-range-label {
  font-size: var(--fs-2xs);
  fill: var(--text-dim);
  font-family: inherit;
}

.cht-gauge-needle {
  transition: transform 0.3s ease;
}

/* Funnel chart */
.cht-funnel-area {
  display: flex;
  justify-content: center;
  /* Defense in depth behind the geometry fix in ui-chart-funnel.js. An SVG does not
     clip its own children, so a band computed larger than the viewBox escapes the
     widget and paints over whatever is next to it — it covered the metric tiles and
     both charts on the Policies dashboard. The scale fix means this should no longer
     be reachable; keeping the clip means a future arithmetic slip degrades into a
     cropped chart inside its own panel rather than ink across the whole dashboard. */
  overflow: hidden;
}

.cht-funnel-stage {
  transition: opacity 0.15s;
  /* No cursor: pointer — see .cht-donut-segment. [ND-1077] */
}

.cht-funnel-stage:hover {
  opacity: 0.85;
}

.cht-funnel-label {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  fill: var(--text);
  font-family: inherit;
  pointer-events: none;
}

.cht-funnel-value {
  font-size: var(--fs-sm);
  fill: rgba(255, 255, 255, 0.7);
  font-family: inherit;
  pointer-events: none;
}

.cht-funnel-conversion {
  font-size: var(--fs-xs);
  fill: var(--text-dim);
  font-family: inherit;
}

/* Heatmap */
.cht-heatmap-area {
  display: flex;
  justify-content: center;
  overflow-x: auto;
}

.cht-heatmap-cell {
  transition: opacity 0.15s;
  /* No cursor: pointer — see .cht-donut-segment. [ND-1077] */
}

.cht-heatmap-cell:hover {
  opacity: 0.8;
  stroke: var(--text);
  stroke-width: 1;
}

.cht-heatmap-cell-value {
  font-size: var(--fs-2xs);
  fill: var(--text);
  font-family: inherit;
  pointer-events: none;
}

/* Scatter chart */
.cht-scatter-point {
  transition: r 0.15s, opacity 0.15s;
  /* No cursor: pointer — see .cht-donut-segment. [ND-1077] */
}

.cht-scatter-point:hover {
  opacity: 1 !important;
}

/* Treemap */
.cht-treemap-area {
  display: flex;
  justify-content: center;
}

.cht-treemap-rect {
  transition: opacity 0.15s;
  /* No cursor: pointer — see .cht-donut-segment. [ND-1077] */
}

.cht-treemap-rect:hover {
  opacity: 0.8;
  stroke: var(--text);
  stroke-width: 1;
}

.cht-treemap-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  fill: var(--text);
  font-family: inherit;
  pointer-events: none;
}

.cht-treemap-value {
  font-size: var(--fs-xs);
  fill: rgba(255, 255, 255, 0.65);
  font-family: inherit;
  pointer-events: none;
}

/* Threshold lines */
.cht-threshold-line {
  pointer-events: none;
}

.cht-threshold-label {
  font-size: var(--fs-xs);
  font-family: inherit;
  pointer-events: none;
}

/* Waterfall chart */
.cht-waterfall-area {
  display: flex;
  justify-content: center;
}

/* Bullet chart */
.cht-bullet-area {
  display: flex;
  justify-content: center;
}

.cht-bullet-bar {
  transition: width 0.3s ease;
}

.cht-bullet-target {
  pointer-events: none;
}

/* Progress ring */
.cht-progress-ring-area {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.cht-progress-arc {
  transition: stroke-dasharray 0.6s ease;
}

/* Radar chart */
.cht-radar-area {
  display: flex;
  justify-content: center;
}

.cht-radar-fill {
  pointer-events: none;
}

.cht-radar-dot {
  transition: r 0.15s;
}

.cht-radar-dot:hover {
  r: 5;
}

/* Sankey chart */
.cht-sankey-area {
  display: flex;
  justify-content: center;
  overflow-x: auto;
}

.cht-sankey-link {
  transition: opacity 0.2s;
}

.cht-sankey-link:hover {
  opacity: 0.5 !important;
}

.cht-sankey-node {
  /* No cursor: pointer — see .cht-donut-segment. [ND-1077] */
  transition: opacity 0.15s;
}

.cht-sankey-node:hover {
  opacity: 0.85;
}

/* ── Chart Animations ── */
@keyframes cht-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes cht-line-draw {
  to { stroke-dashoffset: 0; }
}


@keyframes cht-gauge-sweep {
  from { transform: rotate(-120deg); }
  to   { transform: rotate(0deg); }
}

/* ══════════════════════════════════════════════════════════════════
   HTML PREVIEW — <ui-html-preview>
   ══════════════════════════════════════════════════════════════════ */

ui-html-preview {
  display: block;
}

.hp-label {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--spacing-4);
}

.hp-toolbar {
  display: flex;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
}

.hp-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-2) var(--spacing-5);
  font-size: var(--fs-base);
  font-family: var(--font);
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: all 0.15s;
}

.hp-btn:hover {
  color: var(--text);
  background: var(--surface-2);
}

.hp-btn svg {
  width: 14px;
  height: 14px;
}

.hp-container {
  border: 1px solid var(--border);
  border-radius: 0 0 var(--radius) var(--radius);
  overflow: auto;
  background: var(--white);
}

.hp-frame {
  display: block;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--white);
}

a.hp-btn {
  text-decoration: none;
}

.hp-empty {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-8);
  color: var(--text-dim);
  font-size: var(--fs-md);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.hp-empty svg {
  width: 16px;
  height: 16px;
  opacity: 0.5;
}

/* ══════════════════════════════════════════════════════════════════
   JSON VIEW — <ui-json-view>
   ══════════════════════════════════════════════════════════════════ */

ui-json-view {
  display: block;
}

.jv-label {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: var(--spacing-4);
}

.jv-toolbar {
  display: flex;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
}

.jv-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-2) var(--spacing-5);
  font-size: var(--fs-base);
  font-family: var(--font);
  color: var(--text-muted);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: all 0.15s;
}

.jv-btn:hover {
  color: var(--text);
  background: var(--surface-2);
}

.jv-btn svg {
  width: 14px;
  height: 14px;
}

.jv-container {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 0 0 var(--radius) var(--radius);
  padding: var(--spacing-7);
  overflow: auto;
  font-family: var(--mono);
  font-size: var(--fs-md);
  line-height: var(--lh-loose);
}

.jv-empty {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-8);
  color: var(--text-dim);
  font-size: var(--fs-md);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.jv-empty svg {
  width: 16px;
  height: 16px;
  opacity: 0.5;
}

.jv-toggle {
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
}

.jv-toggle:hover {
  opacity: 0.8;
}

.jv-arrow {
  display: inline-block;
  width: 12px;
  font-size: var(--fs-xs);
  color: var(--text-dim);
  transition: transform 0.15s;
}

.jv-node:not(.jv-collapsed) > .jv-toggle > .jv-arrow {
  transform: rotate(90deg);
}

.jv-brace {
  color: var(--text-muted);
  font-weight: var(--fw-semibold);
}

.jv-count {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  margin-left: var(--spacing-3);
}

.jv-node:not(.jv-collapsed) > .jv-toggle > .jv-count {
  display: none;
}

.jv-children {
  padding-left: var(--spacing-7);
  border-left: 1px solid var(--border);
  margin-left: var(--spacing-3);
}

.jv-node.jv-collapsed > .jv-children,
.jv-node.jv-collapsed > .jv-close {
  display: none;
}

.jv-entry {
  padding: var(--spacing-1) 0;
}

.jv-key {
  color: var(--accent);
}

.jv-index {
  color: var(--text-dim);
}

.jv-colon {
  color: var(--text-dim);
}

.jv-string {
  color: var(--success);
  word-break: break-all;
}

.jv-number {
  color: var(--info);
}

.jv-boolean {
  color: var(--warning);
  font-weight: var(--fw-semibold);
}

.jv-null {
  color: var(--danger);
  font-style: italic;
}

.jv-close {
  display: block;
}

/* ─── Editable panel: hide form fields in view mode when has rich viewer ─── */
ui-editable-panel[view-hidden] > ui-form-field {
  display: none;
}
ui-editable-panel[view-hidden][data-editing="true"] > ui-form-field {
  display: block;
}
ui-editable-panel[view-hidden]:not([data-editing="true"]) + ui-code-block,
ui-editable-panel[view-hidden]:not([data-editing="true"]) + ui-json-view,
ui-editable-panel[view-hidden]:not([data-editing="true"]) + ui-report-parameters {
  display: block;
}
ui-editable-panel[view-hidden][data-editing="true"] + ui-code-block,
ui-editable-panel[view-hidden][data-editing="true"] + ui-json-view,
ui-editable-panel[view-hidden][data-editing="true"] + ui-report-parameters {
  display: none;
}

/* ══════════════════════════════════════════════════════════════════
   CODE BLOCK — <ui-code-block>
   ══════════════════════════════════════════════════════════════════ */

ui-code-block {
  display: block;
}
.cb-empty {
  padding: var(--spacing-7);
  color: var(--text-dim);
  font-style: italic;
  font-size: var(--fs-md);
}
.cb-container {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: auto;
  font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Cascadia Code', monospace;
  font-size: 12.5px;
  line-height: var(--lh-loose);
}
.cb-table {
  border-collapse: collapse;
  width: 100%;
}
.cb-line:hover {
  background: var(--surface-2);
}
.cb-num {
  padding: 0 var(--spacing-6) 0 var(--spacing-6);
  text-align: right;
  color: var(--text-dim);
  user-select: none;
  font-size: var(--fs-sm);
  vertical-align: top;
  white-space: nowrap;
  border-right: 1px solid var(--border);
}
.cb-code {
  padding: 0 var(--spacing-6);
  white-space: pre;
  color: var(--text);
}
.cb-table tr:first-child .cb-num,
.cb-table tr:first-child .cb-code {
  padding-top: var(--spacing-6);
}
.cb-table tr:last-child .cb-num,
.cb-table tr:last-child .cb-code {
  padding-bottom: var(--spacing-6);
}

/* SQL syntax */
.cb-keyword { color: var(--accent); font-weight: var(--fw-semibold); }
.cb-string { color: var(--success); }
.cb-number { color: var(--info); }
.cb-comment { color: var(--text-dim); font-style: italic; }
.cb-param { color: var(--warning); background: rgba(245, 158, 11, 0.1); border-radius: var(--radius-2xs); padding: 0 3px; }

/* HTML syntax */
.cb-tag { color: var(--danger); }
.cb-attr { color: var(--warning); }

/* JSON syntax */
.cb-key { color: var(--accent); }


/* ══════════════════════════════════════════════════════════════════
   HOC — <ui-address> Higher-Order Address Component
   ══════════════════════════════════════════════════════════════════ */

ui-address {
  display: block;
}

ui-address[full-width] {
  grid-column: 1 / -1;
}

/* Label above the address block */
.addr-label {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: var(--spacing-4);
}

/* Edit mode field grid */
.addr-fields {
  display: grid;
  grid-template-columns: repeat(var(--addr-columns, 3), 1fr);
  gap: var(--spacing-6) var(--spacing-6);
}

.addr-field {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

/* Grid placement helpers */
.addr-full { grid-column: 1 / -1; }
.addr-city { grid-column: span 1; }
.addr-state { grid-column: span 1; }
.addr-zip { grid-column: span 1; }
.addr-half { grid-column: span 1; }

/* On narrow containers (2 columns), city/state/zip get separate rows */
@container (max-width: 400px) {
  .addr-fields {
    grid-template-columns: 1fr 1fr;
  }
  .addr-city { grid-column: 1 / -1; }
  .addr-state { grid-column: span 1; }
  .addr-zip { grid-column: span 1; }
}

/* View mode */
.addr-view {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

.addr-view-row {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-4);
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

.addr-view-key {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  min-width: 60px;
}

.addr-view-val {
  color: var(--text);
}

.addr-view-empty {
  font-size: var(--fs-md);
  color: var(--text-muted);
  font-style: italic;
}

/* Compact mode */
.addr-compact {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-4);
}

.addr-compact-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.addr-compact-value {
  font-size: var(--fs-md);
  color: var(--text);
}

/* ─── Address autocomplete + validation (ND-770) ───
   Every rule below is reachable only from a <ui-address> that authored a
   suggest-method or a validate-method. An element with neither renders exactly the
   nodes it rendered before this block existed, which is what keeps 261 authored
   files unchanged. */

/* The containing block for the dropdown. Only the street line gets it, because only
   the street line has a list. */
.addr-field-suggest {
  position: relative;
}

/* Mirrors .ff-ta-dropdown deliberately: two type-aheads in one form that drop their
   menus differently read as two different controls. --z-menu is the LOCAL band (1-100)
   because this list stays inside its own subtree — it is not body-mounted. */
.addr-suggest {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: var(--spacing-2);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--elev-menu);
  max-height: 240px;
  overflow-y: auto;
  z-index: var(--z-menu);
}

/* A geocoder label is one long line — "2901 E Madison St, Seattle, WA 98112-4213,
   United States" — so it wraps rather than being clipped at the menu edge, and it
   wraps mid-token because a postal code with no spaces in it is not breakable at a
   word boundary. Same reasoning as .ff-lookup-status: this text is remote, so no
   length is safe. */
.addr-suggest-item {
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-md);
  line-height: var(--lh-normal);
  color: var(--text);
  cursor: pointer;
  min-width: 0;
  overflow-wrap: anywhere;
}

.addr-suggest-item:hover,
.addr-suggest-item-active {
  background: var(--accent-dim);
}

.addr-suggest-empty {
  padding: var(--spacing-4) var(--spacing-5);
  font-size: var(--fs-base);
  color: var(--text-dim);
  text-align: center;
}

/* The correction panel. Quiet on purpose: it appears under a block the user has just
   finished filling in, and a saturated banner at that moment reads as a failure of
   the thing that just worked (the .ff-lookup-status argument, one element over). */
.addr-correction {
  grid-column: 1 / -1;
  margin-top: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-5);
  border: 1px solid var(--border);
  border-left: 2px solid var(--warning);
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
}

.addr-diff-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: var(--spacing-3);
}

/* minmax(0, …) on every track, not 1fr: a grid track is min-content-sized by default,
   so one unbreakable token in a remote value widens the whole panel past its
   container instead of wrapping inside its own cell. */
.addr-diff-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr) minmax(0, 1.6fr);
  gap: var(--spacing-4);
  align-items: baseline;
  padding: var(--spacing-2) 0;
  font-size: var(--fs-md);
  line-height: var(--lh-normal);
}

.addr-diff-head {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.addr-diff-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The two sides are visually distinguishable WITHOUT relying on position alone: the
   old value is struck through and dimmed, the new one is emphasized. A panel whose
   only cue is "left column versus right column" is unreadable to anyone who arrives
   at it from a screen reader, and ambiguous to everyone else. */
.addr-diff-mine {
  color: var(--text-muted);
  text-decoration: line-through;
  min-width: 0;
  overflow-wrap: anywhere;
}

.addr-diff-theirs {
  color: var(--text);
  font-weight: var(--fw-medium);
  min-width: 0;
  overflow-wrap: anywhere;
}

.addr-diff-head .addr-diff-mine,
.addr-diff-blank {
  text-decoration: none;
}

/* "(blank)" is the element saying the user typed nothing here, not a value being
   removed — so it reads as a note rather than as text under a strike. */
.addr-diff-blank {
  font-style: italic;
  color: var(--text-dim);
}

.addr-diff-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3);
  margin-top: var(--spacing-4);
}

/* The status line. :empty is display:none so an element that has never called out
   contributes no vertical space and no described-by target. */
.addr-status {
  grid-column: 1 / -1;
  margin-top: var(--spacing-3);
  padding: var(--spacing-2) var(--spacing-3);
  border-left: 2px solid transparent;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-muted);
  min-width: 0;
  overflow-wrap: anywhere;
}

.addr-status:empty {
  display: none;
}

.addr-status-ok {
  color: var(--success);
  background: var(--success-bg);
  border-left-color: var(--success);
}

.addr-status-warn {
  color: var(--warning);
  background: var(--warning-bg);
  border-left-color: var(--warning);
}

.addr-status-error {
  color: var(--danger);
  background: var(--danger-bg);
  border-left-color: var(--danger);
}

.addr-status-info {
  color: var(--text-muted);
  background: var(--info-bg);
  border-left-color: var(--info);
}

/* A field whose value is not the user's to type: a live copy of a sibling
   (copy-from-key, ND-855), or a value derived from the record a sibling has selected
   (copy-from-option-key). The element sets the platform `inert` property on itself,
   which makes the whole subtree non-focusable and non-interactive; this is the only
   thing that SAYS so. Without it the control looks editable, every keystroke is
   overwritten by the next pass, and the user is left guessing. Same three
   declarations as .ff-input[readonly], deliberately: it is the same statement — and
   `readonly` itself is NOT usable for either feature, because on ui-form-field it
   renders display text with no input at all, so a readonly required field can never
   be filled and its form can never be submitted.

   A mirror is inert only while its copy is on and is usually hidden as well, in
   which case this is moot — but "usually" is not a contract. A derived field is
   inert for its whole life and is normally on screen, so for that one this rule is
   the whole of the affordance. */
ui-address[inert] .ff-input,
ui-form-field[inert] .ff-input {
  opacity: 0.6;
  cursor: not-allowed;
  background: var(--bg);
}

/* ══════════════════════════════════════════════════════════════════
   FORMS — <ui-collection-field> Editable Child Collection
   ══════════════════════════════════════════════════════════════════ */

ui-collection-field {
  display: block;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

ui-collection-field.colf-invalid {
  border-color: var(--danger);
}

/* ── Head: label, count, add ─────────────────────────────────────── */

ui-collection-field .colf-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-4);
  border-bottom: 1px solid var(--border);
}

ui-collection-field .colf-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-collection-field .colf-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 var(--spacing-2);
  border-radius: var(--radius-full);
  background: var(--surface-3);
  color: var(--text-muted);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
}

ui-collection-field .colf-head-gap {
  flex: 1;
}

ui-collection-field .colf-add {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-2) var(--spacing-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
}

ui-collection-field .colf-add:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ── Summary table ───────────────────────────────────────────────── */

ui-collection-field .colf-hrow,
ui-collection-field .colf-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-2) var(--spacing-4);
}

ui-collection-field .colf-hrow {
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

ui-collection-field .colf-row {
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text);
}

ui-collection-field .colf-body > .colf-row:last-child {
  border-bottom: none;
}

ui-collection-field .colf-row:hover {
  background: var(--surface-2);
}

/* The row currently loaded in the editor. A left rule rather than a background,
   so it stays legible over the hover state above. */
ui-collection-field .colf-row-editing {
  box-shadow: inset 2px 0 0 0 var(--accent);
  background: var(--surface-2);
}

ui-collection-field .colf-hcell,
ui-collection-field .colf-cell {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-collection-field .colf-primary {
  font-weight: var(--fw-semibold);
}

ui-collection-field .colf-blank,
ui-collection-field .colf-muted {
  color: var(--text-dim);
}

/* Three distinct outcomes for render="boolean": yes, no, and NOT ASKED (the
   em-dash from .colf-blank). A tick-or-nothing renderer would show `false` and
   an absent key identically, which is the conflation this element refuses. */
ui-collection-field .colf-yes {
  color: var(--success);
}

ui-collection-field .colf-no {
  color: var(--text-dim);
}

ui-collection-field .colf-cell svg {
  vertical-align: text-bottom;
}

/* A bare .badge carries no tone, so render="badge" would otherwise be bold text
   with padding and no pill. Scoped here rather than given a tone in the global
   rule, which every other consumer of .badge would inherit. */
ui-collection-field .colf-cell .badge {
  padding: var(--spacing-1) var(--spacing-3);
  background: var(--surface-3);
  color: var(--text-muted);
  font-size: var(--fs-2xs);
}

/* ── Row actions ─────────────────────────────────────────────────── */

ui-collection-field .colf-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-1);
  min-width: 84px;
}

ui-collection-field .colf-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  opacity: 0.6;
}

ui-collection-field .colf-icon:hover:not(:disabled) {
  opacity: 1;
  color: var(--accent);
  background: var(--surface-3);
}

ui-collection-field .colf-icon.colf-remove:hover:not(:disabled) {
  color: var(--danger);
}

ui-collection-field .colf-icon:disabled {
  opacity: 0.2;
  cursor: default;
}

ui-collection-field .colf-icon svg {
  color: inherit;
}

/* ── Empty state ─────────────────────────────────────────────────── */

ui-collection-field .colf-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-2);
  padding: var(--spacing-6) var(--spacing-4);
  color: var(--text-dim);
  font-size: var(--fs-sm);
}

/* ── Row editor ──────────────────────────────────────────────────── */

/* Visibility is a projection of `_editing`, never the state itself
   (invariant 7). Nothing reads this rule back to ask what is open. */
ui-collection-field .colf-editor {
  display: none;
}

ui-collection-field.colf-editing .colf-editor {
  display: block;
  padding: var(--spacing-4);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

ui-collection-field .colf-editor-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--spacing-2);
  margin-top: var(--spacing-4);
}

ui-collection-field .colf-btn {
  padding: var(--spacing-2) var(--spacing-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface);
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: var(--fw-medium);
  cursor: pointer;
}

ui-collection-field .colf-btn-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--text-on-accent);
}

ui-collection-field .colf-btn-ghost:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* ── Error ───────────────────────────────────────────────────────── */

ui-collection-field .colf-error:not(:empty) {
  display: block;
  padding: var(--spacing-2) var(--spacing-4) var(--spacing-3);
  color: var(--danger);
  font-size: var(--fs-xs);
}

/* ══════════════════════════════════════════════════════════════════
   HOC — <ui-location-map> Location Map Component
   ══════════════════════════════════════════════════════════════════ */

ui-location-map {
  display: block;
  container-type: inline-size;
}

.lm-container {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}

.lm-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-5) var(--spacing-7);
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}

.lm-header-left {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

.lm-header-left svg {
  width: 16px;
  height: 16px;
  color: var(--accent);
}

.lm-header-right {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}

.lm-geocode-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-on-accent);
  background: var(--accent);
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
  transition: opacity 0.15s;
}

.lm-geocode-btn:hover:not(:disabled) {
  opacity: 0.85;
}

.lm-geocode-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.lm-geocode-btn svg {
  width: 14px;
  height: 14px;
}

.lm-banner {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-5) var(--spacing-7);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
}

.lm-banner svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

.lm-banner-info {
  background: var(--accent-dim);
  color: var(--info, var(--accent));
  border-bottom: 1px solid var(--border);
}

.lm-body {
  display: flex;
}

.lm-sidebar {
  width: 240px;
  min-width: 240px;
  border-right: 1px solid var(--border);
  overflow-y: auto;
  max-height: 500px;
  background: var(--surface);
}

.lm-list-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  padding: var(--spacing-5) var(--spacing-6);
  border-bottom: 1px solid var(--border);
  cursor: default;
  transition: background 0.1s;
}

.lm-list-item.lm-geocoded {
  cursor: pointer;
}

.lm-list-item.lm-geocoded:hover {
  background: var(--surface-2);
}

.lm-list-marker {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-circle);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: white;
  flex-shrink: 0;
}

.lm-list-info {
  min-width: 0;
}

.lm-list-label {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lm-list-desc {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.lm-map-container {
  flex: 1;
  min-width: 0;
}

.lm-loading,
.lm-error,
.lm-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-6);
  font-size: var(--fs-md);
  color: var(--text-muted);
}

.lm-error {
  color: var(--danger);
}

.lm-error svg,
.lm-empty svg {
  width: 32px;
  height: 32px;
  opacity: 0.5;
}

.lm-spinner {
  width: 24px;
  height: 24px;
  border: 3px solid var(--border);
  border-top-color: var(--accent);
  border-radius: var(--radius-circle);
  animation: lm-spin 0.8s linear infinite;
}

/* currentColor, not --text/--text-dim: this variant is rendered ONLY inside
   .lm-geocode-btn (ui-location-map.js:417), which is an --accent fill, so the
   page-level text tokens are the wrong scope — --text is #eeeeee on trident's
   lime, 1.28:1. Its sibling .lm-spinner above is on a --surface background and
   correctly keeps --border/--accent. */
.lm-spinner-sm {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid color-mix(in srgb, currentColor 40%, transparent);
  border-top-color: currentColor;
  border-radius: var(--radius-circle);
  animation: lm-spin 0.8s linear infinite;
}

@keyframes lm-spin {
  to { transform: rotate(360deg); }
}

@container (max-width: 600px) {
  .lm-body {
    flex-direction: column;
  }
  .lm-sidebar {
    width: 100%;
    min-width: 100%;
    max-height: 200px;
    border-right: none;
    border-bottom: 1px solid var(--border);
  }
}

/* ══════════════════════════════════════════════════════════════════
   UTILITIES
   ══════════════════════════════════════════════════════════════════ */

/* Config-only elements should not render */
ui-sidebar-item,
ui-list-search,
ui-list-statuses,
ui-list-columns,
ui-list-new-item,
ui-search-field,
ui-match-type,
ui-status,
ui-column,
ui-field,
ui-option,
ui-status-color,
ui-hero-action,
ui-nav-section,
ui-nav-item,
ui-actions,
ui-action,
ui-table-column,
ui-table-action,
ui-dialog-action,
ui-tab,
ui-list-field,
ui-series,
ui-threshold,
ui-kanban-column,
ui-collection-column,
ui-kanban-card-field {
  display: none;
}

/* ── Kanban Board ──────────────────────────────────────────────────── */

ui-kanban-board {
  display: block;
}

.kanban-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 0 var(--spacing-6) 0;
  flex-wrap: wrap;
  gap: var(--spacing-4);
}

.kanban-item-total {
  font-size: var(--fs-base);
  color: var(--text-muted);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}

.kanban-toolbar {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  flex-wrap: wrap;
}

.kanban-select {
  background: var(--surface-2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-base);
  cursor: pointer;
  appearance: auto;
}

.kanban-select:focus {
  outline: 1px solid var(--info);
}

.kanban-truncation {
  padding: var(--spacing-3) var(--spacing-6);
  margin-bottom: var(--spacing-5);
  background: var(--warning-bg);
  border-radius: var(--radius-sm);
  font-size: var(--fs-base);
  color: var(--warning);
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  flex-wrap: wrap;
}

.kanban-load-rest {
  background: var(--surface-3);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  padding: var(--spacing-2) var(--spacing-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
}

.kanban-load-rest:hover {
  background: var(--surface-2);
}

.kanban-board {
  display: flex;
  gap: var(--spacing-6);
  overflow-x: auto;
  height: calc(100vh - 220px);
  min-height: 300px;
  scrollbar-width: thin;
  scrollbar-color: var(--text-dim) transparent;
}

.kanban-board::-webkit-scrollbar {
  height: 6px;
}

.kanban-board::-webkit-scrollbar-track {
  background: transparent;
}

.kanban-board::-webkit-scrollbar-thumb {
  background: var(--text-dim);
  border-radius: var(--radius-2xs);
}

.kanban-board::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
}

.kanban-col {
  flex: 1 0 0;
  min-width: 220px;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
  border-radius: var(--radius);
  overflow: hidden;
}

.kanban-col-header {
  padding: var(--spacing-5) var(--spacing-6) var(--spacing-4);
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  transition: background 0.15s;
  position: sticky;
  top: 0;
  z-index: var(--z-raised);
  background: var(--surface-2);
  flex-shrink: 0;
}

.kanban-col-header:hover {
  background: var(--surface-2);
}

.kanban-col-header-top {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}

.kanban-col-subtitle {
  margin-top: var(--spacing-2);
  font-size: var(--fs-xs);
  line-height: var(--lh-snug);
  color: var(--text-muted);
  font-weight: var(--fw-normal);
}

.kanban-col-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-circle);
  flex-shrink: 0;
}

.kanban-col-label {
  flex: 1;
}

.kanban-col-count {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  color: var(--text-muted);
  background: var(--surface-3);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-md);
  font-variant-numeric: tabular-nums;
}

.kanban-col-body {
  flex: 1;
  padding: var(--spacing-4);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--text-dim) transparent;
}

.kanban-col-body::-webkit-scrollbar {
  width: 4px;
}

.kanban-col-body::-webkit-scrollbar-track {
  background: transparent;
}

.kanban-col-body::-webkit-scrollbar-thumb {
  background: var(--text-dim);
  border-radius: var(--radius-xs);
}

.kanban-col-body::-webkit-scrollbar-thumb:hover {
  background: var(--text-muted);
}

.kanban-col-empty {
  padding: var(--spacing-7) var(--spacing-4);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-base);
}

.kanban-card {
  padding: var(--spacing-4) var(--spacing-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid transparent;
  border-radius: var(--radius-sm);
  transition: border-color 0.15s, background 0.15s;
}

/* Only a card that actually navigates. The pointer cursor and the hover lift used
   to be on every card, so a board authoring no card-click-view offered a control
   that does nothing — and `keyboard-operable` correctly reads a pointer-cursor
   element with no keyboard path as mouse-only. The class is added by
   _bindEvents(), which is where the board already decides that question. */
.kanban-card-clickable {
  cursor: pointer;
}

.kanban-card-clickable:hover {
  background: var(--surface-2);
  border-color: var(--border);
}

.kanban-card-chip {
  display: inline-block;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  /* Tokenized on merge (invariant 5). Was `1px 6px`; there is no 1px spacing
     token, so the vertical padding becomes 2px — a hair taller on a small
     uppercase chip. Raising the lint baseline instead was not an option. */
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-2xs);
  margin-bottom: var(--spacing-2);
}

.kanban-card-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: var(--lh-normal);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.kanban-card-meta {
  margin-top: var(--spacing-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-tight);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.kanban-card-footer {
  margin-top: var(--spacing-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-3);
  font-size: var(--fs-sm);
}

.kanban-card-footer-left {
  color: var(--text-muted);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
}

.kanban-card-footer-right {
  color: var(--text-muted);
  font-size: var(--fs-xs);
  white-space: nowrap;
  flex-shrink: 0;
}

.kanban-badge {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  padding: var(--spacing-2) var(--spacing-5);
  border-radius: var(--radius-full);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: capitalize;
  background: var(--info-bg);
  color: var(--info);
}

.kanban-loading,
.kanban-error {
  padding: var(--spacing-10);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-md);
}

.kanban-error {
  color: var(--danger);
}

/* ── Detail view: the entity did not load (ND-965) ──────────────────
   The authored regions stay in the DOM because they belong to the consumer, so
   the host attribute hides them and the banner speaks instead. Without this the
   chrome renders bound to nothing — a hero showing a raw UUID and every field an
   em dash — which is indistinguishable from a real record with blank fields. */

ui-detail-view[load-failed] > :not(.dv-load-error) {
  display: none;
}

.dv-load-error {
  padding: var(--spacing-10);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-md);
}

.dv-load-error-title {
  color: var(--danger);
  font-weight: var(--fw-semibold);
  margin-bottom: var(--spacing-2);
}

/*
  The product shell did not mount and the platform's GENERIC view is on screen.
  Distinct from .dv-load-error above in one important way: the record LOADED, so
  nothing is hidden — there is no `ui-detail-view[shell-fallback] > :not(…)` rule.
  The banner sits above a fully usable generic view and says which one you are
  looking at, because a generic view is indistinguishable from a product's own.
*/
/*
  ui-detail-view is a two-row grid (hero, then sidebar+content). A banner prepended
  into it without explicit placement takes row 1 / column 1 and displaces the hero —
  which spans 1/-1 and therefore cannot share that row — down to an implicit row
  BELOW the content. Observed, not predicted: the first render of this banner put the
  Flight Path hero at the bottom of the page under the panels.

  So the fallback state declares a third row and renumbers the other three. Keyed on
  the host attribute, which only exists while the banner does (invariant 7).
*/
ui-detail-view[shell-fallback] {
  grid-template-rows: auto auto minmax(0, 1fr);
}

ui-detail-view[shell-fallback] > .dv-shell-fallback {
  grid-column: 1 / -1;
  grid-row: 1;
}

ui-detail-view[shell-fallback] > :is(ui-detail-hero, ui-policy-hero, ui-policy-header) {
  grid-row: 2;
}

ui-detail-view[shell-fallback] > :is(ui-detail-sidebar, ui-policy-nav),
ui-detail-view[shell-fallback] > ui-detail-content {
  grid-row: 3;
}

/*
  BOTH STATES AT ONCE: banner, hero, action bar, working area. Stated rather than
  left to specificity, because the two blocks disagree — an attribute selector
  outranks a type selector, so the [shell-fallback] rules above would win over the
  :has(> ui-detail-actions) rules and put the nav back in the row the action bar is
  occupying. Four rows is the only arrangement in which neither region is on top of
  the other, and there is no combination of the two blocks that produces it.
*/
ui-detail-view[shell-fallback]:has(> ui-detail-actions) {
  grid-template-rows: auto auto auto minmax(0, 1fr);
}
ui-detail-view[shell-fallback]:has(> ui-detail-actions) > ui-detail-actions {
  grid-row: 3;
}
ui-detail-view[shell-fallback]:has(> ui-detail-actions) > :is(ui-detail-sidebar, ui-policy-nav),
ui-detail-view[shell-fallback]:has(> ui-detail-actions) > ui-detail-content {
  grid-row: 4;
}

.dv-shell-fallback {
  padding: var(--spacing-4);
  background: var(--warning-bg);
  border-bottom: 1px solid var(--warning);
  color: var(--text);
  font-size: var(--fs-md);
}

.dv-shell-fallback-title {
  color: var(--warning);
  font-weight: var(--fw-semibold);
  margin-bottom: var(--spacing-2);
}

.dv-shell-fallback-sub {
  color: var(--text-muted);
}

.dv-shell-fallback-url {
  margin-top: var(--spacing-2);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--text-dim);
  word-break: break-all;
}

/* ── View Switcher (Segmented Control) ─────────────────────────────── */

ui-view-switcher {
  display: flex;
  align-items: center;
}

.vs-segmented {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-1);
  height: 32px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--spacing-1);
  box-sizing: border-box;
}

.vs-segment {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: 0 var(--spacing-5);
  height: 100%;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  white-space: nowrap;
}

.vs-segment:hover {
  color: var(--text);
  background: var(--surface-3);
}

.vs-segment[aria-pressed="true"] {
  background: var(--accent);
  color: var(--text-on-accent);
}

.vs-settings-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-left: var(--spacing-2);
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
}

.vs-settings-btn:hover {
  color: var(--text);
  background: var(--surface-3);
}

/* ── Dashboard Toolbar ─────────────────────────────────────────────── */

ui-dashboard-toolbar {
  display: block;
  padding: 0 0 var(--spacing-4) 0;
}

ui-dashboard-toolbar ui-toolbar-sort,
ui-dashboard-toolbar ui-toolbar-filter {
  display: none;
}

.dbt-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  min-height: 36px;
}

.dbt-left {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
}

.dbt-right {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
}

.dbt-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  margin-left: auto;
}

.dbt-actions:empty {
  display: none;
}

.dbt-actions:empty + .dbt-divider {
  display: none;
}

.dbt-actions:empty + .dbt-divider + .dbt-right {
  margin-left: auto;
}

.dbt-divider {
  width: 1px;
  height: var(--spacing-8);
  background: var(--border);
  margin: 0 var(--spacing-3);
}

.dbt-count {
  font-size: var(--fs-base);
  color: var(--text-muted);
  font-weight: var(--fw-medium);
  white-space: nowrap;
}

.dbt-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  height: 32px;
  padding: 0 var(--spacing-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  white-space: nowrap;
}

.dbt-pill:hover {
  border-color: var(--accent);
}

.dbt-pill[aria-expanded="true"] {
  border-color: var(--accent);
  background: var(--surface-3);
}

.dbt-pill .dbt-pill-chevron {
  color: var(--text-muted);
  transition: transform 0.15s;
}

.dbt-pill[aria-expanded="true"] .dbt-pill-chevron {
  transform: rotate(180deg);
}

.dbt-pill-value {
  color: var(--text);
}

/* Uniform height for form-field selects inside the toolbar */
ui-dashboard-toolbar .ff-input {
  height: 32px;
  font-size: var(--fs-base);
}
ui-dashboard-toolbar ui-form-field {
  margin: 0;
}
ui-dashboard-toolbar ui-form-field .ff-group {
  gap: 0;
}
ui-dashboard-toolbar ui-form-field .ff-label {
  display: none;
}

/* ── Dashboard Toolbar Dropdown ────────────────────────────────────── */

.dbt-dropdown {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--spacing-2) 0;
  z-index: var(--z-dropdown);
  box-shadow: var(--elev-dropdown);
  max-height: 280px;
  overflow-y: auto;
  font-family: var(--font);
}

.dbt-dropdown-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-md);
  color: var(--text);
  cursor: pointer;
  transition: background 0.1s;
}

.dbt-dropdown-item:hover {
  background: var(--surface-3);
}

.dbt-dropdown-item.dbt-selected {
  color: var(--accent);
  font-weight: var(--fw-medium);
}

.dbt-dropdown-item ui-icon {
  color: var(--accent);
}

/* ── Date Range Filter (ui-date-range-filter) ──────────────────────
   Layout for the preset dropdown + date inputs inside a dashboard
   toolbar. The controls themselves use .ff-input / .ff-select for
   consistent styling; these rules handle the container flex layout,
   separator text, and toolbar-appropriate sizing. */

ui-date-range-filter {
  display: contents;
}

.drf-container {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
}

.drf-select {
  width: auto;
  min-width: 120px;
}

.drf-date {
  width: auto;
  min-width: 130px;
}

.drf-separator {
  font-size: var(--fs-base);
  color: var(--text-muted);
  flex-shrink: 0;
}

/* Inside a dashboard toolbar, match the 32px compact height */
ui-dashboard-toolbar .drf-select,
ui-dashboard-toolbar .drf-date {
  height: 32px;
  font-size: var(--fs-base);
}

/* ══════════════════════════════════════════════════════════════════
   DRAWER
   ══════════════════════════════════════════════════════════════════ */

ui-drawer {
  display: contents;
}

/* ─── Backdrop ─── */
.drawer-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: var(--z-modal);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  opacity: 0;
  transition: opacity 0.2s ease-out;
}
.drawer-backdrop.drawer-open {
  display: block;
  opacity: 1;
}

/* modal="false". The host div still exists — it is what `display: none` hides
   when the drawer is closed, and what the panel is positioned inside — but it
   paints nothing and swallows no pointer events, so the page behind keeps its
   clicks, its hover and its scroll. `pointer-events` is re-enabled on the panel
   alone; without that line the panel is decorative. */
.drawer-backdrop.drawer-modeless {
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  pointer-events: none;
}
.drawer-backdrop.drawer-modeless .drawer-panel {
  pointer-events: auto;
}

/* ─── Panel ─── */
.drawer-panel {
  position: fixed;
  background: var(--surface);
  border: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  box-shadow:
    var(--elev-modal);
  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
  overflow: hidden;
}

/* ─── Position variants ─── */
.drawer-right {
  top: 0;
  right: 0;
  bottom: 0;
  transform: translateX(100%);
}
.drawer-left {
  top: 0;
  left: 0;
  bottom: 0;
  transform: translateX(-100%);
}
.drawer-top {
  top: 0;
  left: 0;
  right: 0;
  transform: translateY(-100%);
}
.drawer-bottom {
  bottom: 0;
  left: 0;
  right: 0;
  transform: translateY(100%);
}

.drawer-open .drawer-panel {
  transform: translate(0, 0);
}

/* ─── Size variants (horizontal drawers: left/right) ─── */
.drawer-right.drawer-size-sm,
.drawer-left.drawer-size-sm {
  width: 320px;
  max-width: 90vw;
}
.drawer-right.drawer-size-md,
.drawer-left.drawer-size-md {
  width: 480px;
  max-width: 90vw;
}
.drawer-right.drawer-size-lg,
.drawer-left.drawer-size-lg {
  width: 680px;
  max-width: 90vw;
}
.drawer-right.drawer-size-full,
.drawer-left.drawer-size-full {
  width: 100vw;
}

/* ─── Size variants (vertical drawers: top/bottom) ─── */
.drawer-top.drawer-size-sm,
.drawer-bottom.drawer-size-sm {
  height: 200px;
  max-height: 80vh;
}
.drawer-top.drawer-size-md,
.drawer-bottom.drawer-size-md {
  height: 360px;
  max-height: 80vh;
}
.drawer-top.drawer-size-lg,
.drawer-bottom.drawer-size-lg {
  height: 520px;
  max-height: 80vh;
}
.drawer-top.drawer-size-full,
.drawer-bottom.drawer-size-full {
  height: 100vh;
}

/* ─── Header ─── */
.drawer-header {
  display: flex;
  align-items: center;
  padding: var(--spacing-7) var(--spacing-8);
  gap: var(--spacing-6);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.drawer-title {
  font-size: var(--fs-title);
  font-weight: var(--fw-bold);
  color: var(--text);
  line-height: var(--lh-tight);
  margin: 0;
}
.drawer-close-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: none;
  background: transparent;
  color: var(--text-dim);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition:
    background 0.12s,
    color 0.12s;
  flex-shrink: 0;
}
.drawer-close-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

/* ─── Body ─── */
.drawer-body {
  padding: var(--spacing-7) var(--spacing-8);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  color: var(--text);
  /* The panel is a dark surface and `color-scheme` is `normal` on this document,
     so an overflowing drawer painted Chrome's DEFAULT LIGHT scrollbar down the
     inside edge of a dark panel. Latent until the settings drawer grew a section
     tall enough to scroll; the same declaration already appears twice elsewhere in
     this file for the same reason. */
  scrollbar-color: var(--text-dim) transparent;
}

/* A drawer whose whole body is one self-scrolling element hands the scroll over
   to it. ui-chat-view is `flex: 1; min-height: 0; overflow: hidden` and pins its
   own composer to the bottom, so with the body's own padding and `overflow-y:
   auto` still in force there are two scrollers in one column: the transcript
   scrolls inside a box that also scrolls, and the composer leaves the panel
   instead of staying put. */
.drawer-body:has(> ui-chat-view) {
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

/* ── Settings Drawer ─────────────────────────────────────────────── */

ui-settings-drawer {
  display: contents;
}

.settings-drawer-content {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-8);
}

.settings-section {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-5);
}

.settings-section-title {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}

.settings-section-title svg {
  width: 14px;
  height: 14px;
}

.settings-option-group {
  display: flex;
  gap: var(--spacing-3);
}

.settings-option {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-4) var(--spacing-6);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: var(--fs-md);
  cursor: pointer;
  transition:
    border-color 0.12s,
    color 0.12s,
    background 0.12s;
  flex: 1;
  justify-content: center;
}

.settings-option svg {
  width: 14px;
  height: 14px;
}

.settings-option:hover {
  border-color: var(--text-dim);
  color: var(--text);
}

.settings-option.active {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--accent-dim);
}

/* ─── Toggle Switch ─── */
.settings-toggle-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--spacing-4) 0;
  cursor: pointer;
}

.settings-toggle-label {
  font-size: var(--fs-md);
  color: var(--text);
}

.settings-toggle-input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.settings-toggle-track {
  position: relative;
  width: 36px;
  height: 20px;
  background: var(--surface-3);
  border-radius: var(--radius-md);
  transition: background 0.2s;
  flex-shrink: 0;
}

.settings-toggle-track::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-circle);
  background: var(--text-muted);
  transition:
    transform 0.2s,
    background 0.2s;
}

.settings-toggle-input:checked + .settings-toggle-track {
  background: var(--accent);
}

.settings-toggle-input:checked + .settings-toggle-track::after {
  transform: translateX(16px);
  /* Same as .ff-toggle above: this knob sits on the accent track. Unchecked it is
     --text-muted on --surface-3, which is left alone. */
  background: var(--text-on-accent);
}

/* ── Settings: section 0, the signed-in identity ─────────────────────
   One dim line, no heading. Empty when getCurrentUser has not landed or failed,
   and :empty collapses the flex gap so the panel does not open with a blank
   band above Appearance. */
.settings-identity {
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--text-dim);
}

.settings-identity:empty {
  display: none;
}

/* ── Settings: section 4, Security ───────────────────────────────────
   The ONLY section with a button, and the only one separated by a rule: sections
   1-3 apply instantly, this one submits. The border is the visual statement of
   that difference. */
.settings-security {
  padding-top: var(--spacing-7);
  border-top: 1px solid var(--border);
}

/* An absent section 4 — isPasswordEnabled false — must not leave its border and
   padding behind as an empty ruled strip at the foot of the panel. */
.settings-security:empty {
  display: none;
  padding-top: 0;
  border-top: 0;
}

.scr-loading {
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--text-dim);
}

/* The polite live region. It is present-but-empty for most of the panel's life,
   which is what makes an announcement fire on CHANGE rather than on insertion —
   so it must collapse rather than reserve a gap. */
.scr-status:empty {
  display: none;
}

/* The primary floats at the end of the section: ui-drawer has no footer, and a
   Cancel beside it would have nothing to cancel back to. Closing is the cancel. */
.scr-actions {
  display: flex;
  justify-content: flex-end;
}

.scr-note {
  margin: 0;
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-dim);
}

/* A real <button> rather than an <a>: it performs an action and sends an email,
   it does not navigate anywhere, and a keyboard and a screen reader already
   understand the difference. Styled as inline prose because it sits inside a
   sentence. */
.scr-reset {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--accent);
  text-decoration: underline;
  cursor: pointer;
}

.scr-reset:hover {
  opacity: 0.8;
}

.scr-reset[disabled] {
  cursor: default;
  opacity: 0.5;
}

/* ══════════════════════════════════════════════════════════════════
   CHAT — ui-chat-toolbar, ui-chat-thread, ui-chat-message, ui-chat-input
   ══════════════════════════════════════════════════════════════════ */

/* Chat views need flush layout — no outer padding, no parent scroll */
ui-module:has(ui-chat-thread) {
  overflow: hidden;
}

/* ── Chat Toolbar ────────────────────────────────────────────────── */
ui-chat-toolbar {
  display: block;
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
}

ui-chat-toolbar .chat-toolbar {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-5);
  min-height: 40px;
}

ui-chat-toolbar .chat-toolbar-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}

ui-chat-toolbar .chat-toolbar-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}

ui-chat-toolbar .chat-toolbar-btn svg {
  width: 16px;
  height: 16px;
}

ui-chat-toolbar .chat-toolbar-title {
  flex: 1;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: default;
}

ui-chat-toolbar .chat-toolbar-model {
  font-size: var(--fs-sm);
  padding: var(--spacing-1) var(--spacing-3);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-muted);
}

ui-chat-toolbar .chat-toolbar-select {
  font-size: var(--fs-base);
  padding: var(--spacing-1) var(--spacing-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  max-width: 160px;
}

/* Mouse/programmatic focus: border tint only, no ring. */
ui-chat-toolbar .chat-toolbar-select:focus {
  outline: none;
  border-color: var(--accent);
}

/* ── Focus ring ── */
/* Keyboard focus needs more than a 1px border-color change: --border measures
   1.70:1 against --surface, so the accent-vs-border delta is barely
   perceptible and fails SC 1.4.11 in practice. A real ring is additive here —
   the border tint above still applies. (ND-35; token contrast itself is ND-101) */
ui-chat-toolbar .chat-toolbar-select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

ui-module:has(ui-chat-thread) .view-container {
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

ui-chat-view .chat-view-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--spacing-3);
  padding: var(--spacing-2) var(--spacing-4);
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

ui-chat-view .chat-view-status {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

/* ui-chat-view — data-driven transcript orchestrator. Owns the column layout so the
   thread scrolls and the composer stays pinned to the bottom. */
ui-chat-view {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

/* ── ui-chat-view SPLIT SCREEN ─────────────────────────────────────────
   Conversation list on the left, selected transcript on the right. Opt-in:
   .chat-view-split is written only when list-method is authored, so an
   authored ui-chat-view without one keeps the single column above untouched.

   THE TWO PANES MUST SCROLL INDEPENDENTLY — a long transcript must not scroll
   the list away. That needs three things together, and dropping any one of
   them silently produces one long page-level scroller: the row direction
   here, `min-height: 0` on both panes (a flex item defaults to
   `min-height: auto`, which refuses to shrink below its content and is what
   makes a nested scroller quietly stop scrolling), and the overflow living on
   the inner scrollers — .chat-view-list and ui-chat-thread — rather than on
   the panes. */
ui-chat-view.chat-view-split {
  flex-direction: row;
}

/* `min-width: 0` is NOT decoration here, it is what makes the fixed width a
   fixed width. A flex item defaults to `min-width: auto`, i.e. its min-content
   width, and `.chat-view-item-title` is `white-space: nowrap` — so the widest
   conversation title propagated all the way up and the pane rendered 429px
   wide against a flex-basis of 260px, with `flex-shrink: 0` making that
   permanent. Measured in a live browser, not reasoned about: the basis was
   correct and the box was not. The same reason it repeats on .chat-view-list
   and .chat-view-item below. */
ui-chat-view .chat-view-list-pane {
  display: flex;
  flex-direction: column;
  flex: 0 0 260px;
  min-width: 0;
  min-height: 0;
  border-right: 1px solid var(--border);
  background: var(--surface);
}

ui-chat-view .chat-view-list-head {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: var(--spacing-4);
  padding: var(--spacing-6);
  border-bottom: 1px solid var(--border);
}

/* THE HEAD IS ALWAYS RENDERED when the view is split (see _listPaneHtml), so that
   authoring list options without a create button still produces a kebab to open
   them from. This collapses it when it holds neither control, which is the state
   every existing consumer with a list and no new-method is in — otherwise they
   would all grow an empty padded strip with a rule under it.

   Not a violation of invariant 7: nothing here RECORDS state. The element is
   empty or it is not, and CSS is reading the DOM it was given. */
ui-chat-view .chat-view-list-head:empty {
  display: none;
}

/* `flex: 1` rather than `width: 100%`, because the kebab is now a sibling. With
   no kebab it still grows to the full width, so the single-button case is
   unchanged; with one, the two share the row and the button takes what is left.
   `min-width: 0` for the reason it appears on every box in this pane — a flex
   item's default `min-width: auto` refuses to shrink below its label. */
ui-chat-view .chat-view-list-head ui-button {
  flex: 1;
  min-width: 0;
}

/* The overflow (kebab) trigger. Sized and toned to match .hero-icon-btn, which is
   the same affordance in the same position on ui-detail-hero. */
ui-chat-view .chat-view-list-kebab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  background: transparent;
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s;
}

ui-chat-view .chat-view-list-kebab:hover {
  background: var(--surface-2);
  color: var(--text);
}

ui-chat-view .chat-view-list-kebab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

ui-chat-view .chat-view-list {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  gap: var(--spacing-2);
  padding: var(--spacing-4);
}

ui-chat-view .chat-view-main {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

/* The pending strip's host: a persistent, empty live region between the thread
   and the composer. ui-chat-view puts a `.chat-step-indicator` inside it while a
   send is in flight or the answer is being polled for, and empties it when the
   wait ends — so with nothing waiting this is a childless div with no box of its
   own and the column is laid out exactly as it was before it existed.

   `flex-shrink: 0` for the reason .chat-view-bar has it: the thread above is
   `flex: 1`, and a strip that can be squeezed to nothing is a strip that reports
   a 40-second wait by disappearing. */
ui-chat-view .chat-view-pending {
  flex-shrink: 0;
}

/* ── The unanswered bar ──
   Pinned between the pending strip and the composer, and empty on every healthy
   conversation — same empty-host discipline as the strip above, so a childless
   div has no box and the column is laid out exactly as it was before this
   region existed. Unlike the strip it carries no role and no aria-live: it holds
   a focusable control, and a live region re-announces its text on every rewrite.

   `flex-shrink: 0` for the same reason the strip has it. The notice is the
   `.chat-step-warning` palette — this is the same class of news and rendering it
   in a second set of colors would say it was a different one — but it is a
   distinct box rather than a modifier, because it is bordered, left-aligned
   around a button, and does not live in the live region. */
ui-chat-view .chat-view-unanswered {
  flex-shrink: 0;
}

/* `flex-wrap` plus a flex-basis on the text is what happens in a narrow pane,
   and it was measured rather than guessed: at 320px the sentence wraps to five
   lines beside an 80px button, which left ~70px of dead space under a
   top-aligned button and a 101px bar. Below the basis the button drops to its
   own line instead. A wrap threshold rather than a media query because what
   matters is the pane this element is in, not the viewport. */
ui-chat-view .chat-view-unanswered-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-4);
  margin: var(--spacing-1) var(--spacing-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  font-size: var(--fs-sm);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

/* Aligned to the first line rather than centered, because the text wraps to two
   or three lines in a narrow pane and a centered glyph then floats beside the
   middle of the sentence. */
ui-chat-view .chat-view-unanswered-icon {
  display: flex;
  flex-shrink: 0;
  align-self: flex-start;
  margin-top: var(--spacing-1);
  color: var(--warning);
}

ui-chat-view .chat-view-unanswered-text {
  flex: 1 1 260px;
  min-width: 0;
}

/* The button keeps its own height whatever the text does — with three lines of
   wrapped notice beside it, a stretched primary button reads as a panel. */
ui-chat-view .chat-view-unanswered-note ui-button {
  flex-shrink: 0;
  align-self: flex-start;
}

/* ── The quick-prompt row ──
   Same empty-host discipline as the two regions above: with no prompts-method
   authored, or before the first answer lands, this is a childless div with no
   box and the column is laid out as if it did not exist. `:not(:empty)` is what
   buys that — padding on the host itself would reserve a strip on every chat
   that has no prompts.

   Wrapping first and scrolling second: a handful of pills belong on one or two
   lines, and a board with twenty of them must not push the composer off the
   bottom of a 480px drawer. The cap is three rows deep. */
ui-chat-view .chat-view-prompts {
  flex-shrink: 0;
}

ui-chat-view .chat-view-prompts:not(:empty) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-3) var(--spacing-6) 0;
  max-height: 108px;
  overflow-y: auto;
  scrollbar-color: var(--text-dim) transparent;
}

ui-chat-view .chat-prompt-pill {
  max-width: 100%;
  padding: var(--spacing-2) var(--spacing-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
  line-height: var(--lh-tight);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  transition:
    background 0.12s,
    border-color 0.12s;
}

ui-chat-view .chat-prompt-pill:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

ui-chat-view .chat-prompt-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The unconfigured state is a quiet invitation, not a warning: nobody has done
   anything wrong, and a bordered notice here would read as a failure on every
   board that has simply never set prompts up. */
ui-chat-view .chat-prompt-setup {
  padding: var(--spacing-1) 0;
  border: none;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: var(--fs-sm);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

ui-chat-view .chat-prompt-setup:hover {
  color: var(--text);
}

ui-chat-view .chat-prompt-setup:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

ui-chat-view .chat-prompt-setup-inert {
  text-decoration: none;
  cursor: default;
}

/* ── A list row: title, then the preview and the timestamp beneath it ──
   Two lines is the point of rendering this here rather than in ui-list-view,
   which is one line per row and has no relative-date renderer — so two chats
   from the same day were indistinguishable. */
/* The transparent left border is RESERVED in the resting state so the selected
   state below can fill it without moving any text — see .active. */
ui-chat-view .chat-view-item {
  min-width: 0;
  padding: var(--spacing-5) var(--spacing-6);
  border-left: 2px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background 0.15s;
}

ui-chat-view .chat-view-item:hover {
  background: var(--surface-2);
}

/* SELECTION IS A CLASS (invariant 7). _applySelection() is its only writer,
   this._entityId its only source of truth, and nothing reads it back off the
   DOM to ask what is selected.
   TWO CUES, NOT ONE. --surface-3 against --surface measures 1.34:1, which is
   visible but under the 3:1 floor for a non-text indicator — and it was the
   only cue there was. The accent edge is well clear of it, so a reader who
   cannot resolve the fill still has something to see. (Measured in a live
   browser; the token contrast itself is ND-101 and not fixable here.) */
ui-chat-view .chat-view-item.active {
  border-left-color: var(--accent);
  background: var(--surface-3);
}

ui-chat-view .chat-view-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

ui-chat-view .chat-view-item-title {
  overflow: hidden;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: var(--lh-tight);
  color: var(--text);
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-chat-view .chat-view-item-sub {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-4);
  margin-top: var(--spacing-1);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

ui-chat-view .chat-view-item-preview {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-chat-view .chat-view-item-time {
  flex-shrink: 0;
  font-size: var(--fs-xs);
  color: var(--text-dim);
}

/* ── The list's two NON-ROW states, which MUST NOT look alike ──
   "No conversations yet" and "the call failed" rendering identically is the
   defect ND-347 fixed on ui-list-view, where a query that threw showed an
   empty table beside a New button. The error carries its own color and its
   own alignment as well as its own words. */
ui-chat-view .chat-view-list-note {
  padding: var(--spacing-7) var(--spacing-6);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
  text-align: center;
}

ui-chat-view .chat-view-list-note-error {
  color: var(--danger);
  text-align: left;
}

/* ── Nothing selected ──
   A class plus scoped rules, so no JavaScript writes style.display and nothing
   asks the DOM which pane is showing (invariant 7). */
ui-chat-view .chat-view-empty {
  display: none;
  flex: 1;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-4);
  padding: var(--spacing-10);
  text-align: center;
}

ui-chat-view.chat-view-unselected .chat-view-empty {
  display: flex;
}

ui-chat-view.chat-view-unselected ui-chat-thread,
ui-chat-view.chat-view-unselected ui-chat-input,
ui-chat-view.chat-view-unselected .chat-view-pending,
ui-chat-view.chat-view-unselected .chat-view-bar {
  display: none;
}

ui-chat-view .chat-view-empty-icon {
  color: var(--text-dim);
}

ui-chat-view .chat-view-empty-icon svg {
  width: 28px;
  height: 28px;
}

ui-chat-view .chat-view-empty-title {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-chat-view .chat-view-empty-detail {
  margin: 0;
  max-width: 320px;
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

/* ── A stale link, over the resting state rather than instead of it ──
   The empty pane is centered, so an empty notice slot would push the icon and
   the heading off-center. `:empty` collapses the slot to nothing, the same
   mechanism .chat-view-list-head:empty already uses here. */
ui-chat-view .chat-view-empty-notice {
  width: 100%;
  max-width: 460px;
}

ui-chat-view .chat-view-empty-notice:empty {
  display: none;
}

ui-chat-view .chat-view-not-found {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
  font-size: var(--fs-sm);
  line-height: var(--lh-relaxed);
  color: var(--text);
  text-align: left;
}

ui-chat-view .chat-view-not-found-icon {
  display: flex;
  flex-shrink: 0;
  margin-top: var(--spacing-1);
  color: var(--warning);
}

ui-chat-view .chat-view-not-found-text {
  flex: 1 1 auto;
  min-width: 0;
}

ui-chat-view .chat-view-not-found-dismiss {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  background: transparent;
  cursor: pointer;
  transition:
    background 0.15s,
    color 0.15s;
}

ui-chat-view .chat-view-not-found-dismiss:hover {
  background: var(--surface-2);
  color: var(--text);
}

ui-chat-view .chat-view-not-found-dismiss:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── ui-chat-view's TWO LIST MENUS ─────────────────────────────────────
   The row context menu and the header's option kebab, which share this markup.

   THESE SELECTORS ARE GLOBAL, and they have to be. Both menus portal to
   document.body through ui-popover, so nothing under `ui-chat-view` can reach
   them — the same reason .popover-panel, .switcher-* and .user-menu-* are global.

   WHY THEY PORTAL AT ALL: .chat-view-list above is `overflow-y: auto`, i.e. it is
   the pane's scroller. A dropdown rendered into the row subtree — the
   ui-detail-hero pattern, which was the obvious reuse — is clipped by that
   scroller as soon as it is taller than the space left below the row it opened
   on, and a context menu on the last visible row is exactly that case.

   NO z-index here. ui-popover writes --z-popover onto the host it mounts, and a
   second value on the panel inside it could only disagree with it. */
/* THE SCROLL CAP IS THE VERTICAL HALF OF ui-popover's CLAMP, and it lives here
   rather than on .popover-panel so no existing popover's height changes.
   Measured in a live browser: ui-popover clamps a menu's TOP edge into the
   viewport but cannot shrink it, so a 60-item menu overflowed the bottom by
   866px with nothing to scroll — unreachable, because .popover-panel is
   `overflow: hidden`. That needs ~28 authored items at a laptop viewport, so it
   was latent rather than reachable, and latent is exactly when it is cheap.
   With a cap the panel measures short, which is what makes the clamp correct
   again for any number of items. */
.chat-menu {
  padding: var(--spacing-2) 0;
  max-height: 60vh;
  overflow-y: auto;
}

.chat-menu-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  width: 100%;
  padding: var(--spacing-3) var(--spacing-6);
  border: none;
  font-family: var(--font);
  font-size: var(--fs-md);
  color: var(--text);
  background: none;
  text-align: left;
  cursor: pointer;
}

.chat-menu-item:hover {
  background: var(--surface-3);
}

/* Offset INWARD. .popover-panel is `overflow: hidden`, so the +2px offset used
   everywhere else in this file would put the ring outside the panel and have it
   clipped away — a focus ring that is present and invisible. */
.chat-menu-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.chat-menu-item.danger {
  color: var(--danger);
}

.chat-menu-separator {
  height: 1px;
  margin: var(--spacing-2) 0;
  background: var(--border);
}

/* The leading glyph: an action's icon in the row menu, a checkbox in the option
   menu. Muted while unchecked and accented while checked, so the checked state
   is carried by shape (check-square vs square) as well as by color — WCAG 1.4.1,
   and the two icons differ enough to read at 14px. */
.chat-menu-glyph {
  display: flex;
  flex-shrink: 0;
  color: var(--text-muted);
}

.chat-menu-item[aria-checked='true'] .chat-menu-glyph {
  color: var(--accent);
}

ui-chat-thread {
  display: flex;
  flex-direction: column;
  flex: 1;
  overflow-y: auto;
  padding: var(--spacing-6);
  gap: var(--spacing-4);
  scroll-behavior: smooth;
}

ui-chat-thread .chat-thread-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  flex: 1;
  gap: var(--spacing-4);
  opacity: 0.5;
  padding: var(--spacing-10);
}

ui-chat-thread .chat-thread-empty-icon {
  width: 48px;
  height: 48px;
  color: var(--text-muted);
}

ui-chat-thread .chat-thread-empty-icon svg {
  width: 100%;
  height: 100%;
}

ui-chat-thread .chat-thread-empty-text {
  font-size: var(--fs-lg);
  color: var(--text-muted);
}

/* ── Chat Message ─────────────────────────────────────────── */

ui-chat-message {
  display: flex;
  gap: var(--spacing-4);
  padding: var(--spacing-4);
  border-radius: var(--radius);
  max-width: 100%;
}

ui-chat-message.chat-message-user {
  background: var(--surface-2);
}

ui-chat-message.chat-message-assistant {
  background: transparent;
}

ui-chat-message.chat-message-system {
  background: transparent;
  opacity: 0.7;
  font-size: var(--fs-base);
}

ui-chat-message .chat-message-avatar {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-full);
  background: var(--surface-2);
  color: var(--text-muted);
}

ui-chat-message.chat-message-user .chat-message-avatar {
  background: var(--accent);
  color: var(--text-on-accent);
}

ui-chat-message .chat-message-avatar svg {
  width: 16px;
  height: 16px;
}

ui-chat-message .chat-message-body {
  flex: 1;
  min-width: 0;
}

ui-chat-message .chat-message-header {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-3);
  margin-bottom: var(--spacing-2);
}

ui-chat-message .chat-message-role {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-chat-message .chat-message-time {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.sdk-md {
  font-size: var(--fs-lg);
  line-height: var(--lh-loose);
  color: var(--text);
  word-wrap: break-word;
  overflow-wrap: break-word;
}

.sdk-md p {
  margin: 0 0 var(--spacing-3) 0;
}

.sdk-md p:last-child {
  margin-bottom: 0;
}

.sdk-md h2,
.sdk-md h3,
.sdk-md h4 {
  margin: var(--spacing-4) 0 var(--spacing-2) 0;
  font-weight: var(--fw-semibold);
}

.sdk-md h2 { font-size: var(--fs-xl); }
.sdk-md h3 { font-size: 15px; }
.sdk-md h4 { font-size: var(--fs-lg); }

.sdk-md ul,
.sdk-md ol {
  margin: var(--spacing-2) 0;
  padding-left: var(--spacing-6);
}

.sdk-md li {
  margin-bottom: var(--spacing-1);
}

/* A nested list is indented by its own padding and carries no extra vertical
   gap: a sub-list separated from its parent item reads as a second list.

   Its own padding rather than the inherited --spacing-6: at 12px the indent was
   doing so little that the marker change below was carrying the whole of the
   nesting, and a reader who does not register disc-versus-circle sees one flat
   list. */
.sdk-md li > ul,
.sdk-md li > ol {
  margin: var(--spacing-1) 0 0;
  padding-left: var(--spacing-8);
}

.sdk-md li > ul {
  list-style-type: circle;
}

.sdk-md li > ul > li > ul {
  list-style-type: square;
}

/* A rule between sections of an answer. Subtle on purpose: at full --border
   weight in a 480px column it reads as the end of the message rather than a
   break inside one.

   THE SPACE IS WHAT MAKES IT A BREAK, not the weight. At 10px of margin it sat
   directly under the table above it and read as a second bottom border on that
   table; measured at 1.25:1 against the surface, which is the same contrast as
   every other divider here and correct. Darkening it to compensate would
   desynchronize it from the table borders it sits beside, so the margin is what
   changed. */
.sdk-md .chat-md-rule {
  height: 0;
  margin: var(--spacing-8) 0;
  border: 0;
  border-top: 1px solid var(--border);
  opacity: 0.6;
}

.sdk-md .chat-md-quote {
  margin: var(--spacing-3) 0;
  padding: var(--spacing-1) 0 var(--spacing-1) var(--spacing-4);
  border-left: 2px solid var(--border);
  color: var(--text-muted);
}

.sdk-md a {
  color: var(--accent);
  text-decoration: none;
}

.sdk-md a:hover {
  text-decoration: underline;
}

.sdk-md strong {
  font-weight: var(--fw-semibold);
}

.sdk-md .chat-inline-code {
  font-family: var(--mono);
  font-size: var(--fs-base);
  padding: var(--spacing-1) var(--spacing-2);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}

.sdk-md .chat-code-block {
  font-family: var(--mono);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  padding: var(--spacing-4);
  border-radius: var(--radius);
  background: var(--bg);
  overflow-x: auto;
  margin: var(--spacing-3) 0;
}

.sdk-md .chat-code-block code {
  white-space: pre;
}

/* A markdown table scrolls ITSELF rather than widening the transcript. The chat
   column is 480px in a drawer and the bubble's ancestors are `min-width: 0`, so
   without this wrapper a four-column table pushes every message in the thread
   sideways. `nowrap` on the cells is the other half of the same decision: left
   to wrap, a table in that width collapses to one word per line, which is
   harder to read than scrolling. */
.sdk-md .chat-md-table {
  max-width: 100%;
  margin: var(--spacing-3) 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-x: auto;
  scrollbar-color: var(--text-dim) transparent;
}

.sdk-md .chat-md-table table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
}

.sdk-md .chat-md-table th,
.sdk-md .chat-md-table td {
  padding: var(--spacing-2) var(--spacing-4);
  border-bottom: 1px solid var(--border);
  text-align: left;
  white-space: nowrap;
}

/* Two pixels, not one. In the light palette --surface-2 sits eight values off
   --surface, so the header's tint alone is not a band; the rule under it is what
   makes the row read as a header rather than as the first row of data. */
.sdk-md .chat-md-table th {
  background: var(--surface-2);
  border-bottom-width: 2px;
  font-weight: var(--fw-semibold);
  color: var(--text);
}

.sdk-md .chat-md-table tbody tr:last-child td {
  border-bottom: none;
}

/* Streaming cursor blink */
ui-chat-message .chat-cursor {
  display: inline-block;
  width: 2px;
  height: 14px;
  background: var(--accent);
  margin-left: var(--spacing-1);
  vertical-align: text-bottom;
  animation: chat-cursor-blink 1s step-end infinite;
}

@keyframes chat-cursor-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

/* ── A bubble the server never stored ──
   A DIFFERENT SHAPE, not just a different tint. This bubble sits in the same
   column as the transcript and is the one thing in it that is NOT server truth,
   so the distinction has to survive a reader who cannot tell two grays apart:
   the dashed outline and the warning rule down the leading edge both say it at
   a glance, and the note underneath says it in words for everyone else.

   `--warning` rather than `--danger` for the same reason the unanswered bar
   uses it — nothing has been lost, the message is on screen and retryable.

   ANY state, not just "unsent": the vocabulary is "unsent or nothing", so a
   value this stylesheet does not know is still a bubble refusing to call itself
   server truth, and it must not be left wearing the stored treatment. The
   :not() is what outranks the role class — as two classes they tie at (0,1,1)
   and the stored background returns the day either rule moves. */
ui-chat-message[state]:not([state=""]) {
  background: transparent;
  border: 1px dashed var(--border);
  border-inline-start: 2px solid var(--warning);
}

ui-chat-message[state]:not([state=""]) .chat-message-content {
  color: var(--text-muted);
}

ui-chat-message .chat-message-unsent-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-2);
  margin-top: var(--spacing-3);
  font-size: var(--fs-sm);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

/* Aligned to the first line for the reason the unanswered bar's glyph is: the
   sentence wraps in a narrow pane and a centered glyph floats beside its
   middle. */
ui-chat-message .chat-message-unsent-icon {
  display: flex;
  flex-shrink: 0;
  align-self: flex-start;
  margin-top: var(--spacing-1);
  color: var(--warning);
}

ui-chat-message .chat-message-unsent-text {
  flex: 1 1 220px;
  min-width: 0;
}

/* Outlined rather than filled, and sized exactly as .dr-retry: it is the same
   offer after the same kind of failure, and it is one of potentially several
   controls in a scrolling transcript, where a solid accent button per unsent
   bubble competes with the composer for the reader's eye. */
ui-chat-message .chat-message-retry {
  flex-shrink: 0;
  align-self: flex-start;
  padding: var(--spacing-3) var(--spacing-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text);
  cursor: pointer;
}

ui-chat-message .chat-message-retry:hover {
  background: var(--surface-3);
}

ui-chat-message .chat-message-retry:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Chat Tool Card ──────────────────────────────────────── */

ui-chat-tool-card {
  display: block;
  margin: var(--spacing-1) 0;
  padding: 0 var(--spacing-4);
  font-size: var(--fs-sm);
  line-height: var(--lh-loose);
}

ui-chat-tool-card .chat-tool-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-1) 0;
}

ui-chat-tool-card .chat-tool-icon {
  display: flex;
  width: 12px;
  height: 12px;
  flex-shrink: 0;
}

ui-chat-tool-card .chat-tool-icon svg {
  width: 100%;
  height: 100%;
}

ui-chat-tool-card .chat-tool-icon-running {
  color: var(--accent);
  animation: chat-tool-spin 1s linear infinite;
}

ui-chat-tool-card .chat-tool-icon-success {
  color: var(--success);
}

ui-chat-tool-card .chat-tool-icon-error {
  color: var(--danger);
}

@keyframes chat-tool-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

ui-chat-tool-card .chat-tool-name {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

/* A WORD, not only a glyph. An approved call and a completed read differ by the
   hue of a 12px tick otherwise, and hue alone is not a distinction a reader can
   rely on. Auto margin pushes it away from the name and keeps the expander
   chevron at the right edge. */
ui-chat-tool-card .chat-tool-status {
  margin-left: auto;
  padding: 0 var(--spacing-2);
  border-radius: var(--radius-sm);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-loose);
  color: var(--text-muted);
  background: var(--surface-2);
  flex-shrink: 0;
}

/* THE TINT CARRIES THE SEMANTIC, THE LABEL CARRIES THE TEXT. --success on
   --success-bg measured 3.13:1 in the light palette, which is below AA for 11px
   bold; those tokens are tuned for fills and glyphs, not for type set on top of
   themselves. The status icon beside the badge is still --success, so the color
   signal is unchanged and the word is now as readable as the card around it. */
ui-chat-tool-card .chat-tool-status-approved {
  color: var(--text);
  background: var(--success-bg);
}

ui-chat-tool-card .chat-tool-status-rejected {
  color: var(--text);
  background: var(--surface-3);
}

ui-chat-tool-card .chat-tool-status-error {
  color: var(--text);
  background: var(--danger-bg);
}

/* The one-line trace of a STRING result. An object result has no useful first
   line, so it is only ever in the expander. */
ui-chat-tool-card .chat-tool-preview {
  padding: var(--spacing-1) 0 var(--spacing-1) 18px;
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-chat-tool-card .chat-tool-detail {
  margin: var(--spacing-2) 0 var(--spacing-2) 18px;
  padding: var(--spacing-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
}

ui-chat-tool-card .chat-tool-detail-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}

ui-chat-tool-card .chat-tool-detail-label ~ .chat-tool-detail-label {
  margin-top: var(--spacing-3);
}

/* Pretty-printed JSON keeps its newlines and scrolls sideways rather than
   wrapping mid-token; `break-all` on a serialized payload is unreadable. */
ui-chat-tool-card .chat-tool-detail-body {
  margin: var(--spacing-1) 0 0;
  max-height: 180px;
  overflow: auto;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--text);
  white-space: pre;
  scrollbar-color: var(--text-dim) transparent;
}

ui-chat-tool-card .chat-tool-expand {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  border-radius: var(--radius-sm);
  flex-shrink: 0;
  opacity: 0.6;
}

ui-chat-tool-card .chat-tool-expand:hover {
  opacity: 1;
  background: var(--surface-3);
}

ui-chat-tool-card .chat-tool-expand svg {
  width: 12px;
  height: 12px;
}

/* The human name for a call, shown instead of the raw tool name. Not `--mono`:
   "Searching cards" is a sentence, and setting it in the code face says it is a
   symbol the reader is expected to recognize. */
ui-chat-tool-card .chat-tool-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-chat-tool-card .chat-tool-icon-proposed {
  color: var(--warning);
}

ui-chat-tool-card .chat-tool-icon-approved {
  color: var(--success);
}

ui-chat-tool-card .chat-tool-icon-rejected {
  color: var(--text-muted);
}

/* A proposal is the one tool card the reader has to READ and act on, so it is
   the one that gets a box. The other five statuses stay the quiet one-line
   trace they have always been. */
ui-chat-tool-card.chat-tool-card-proposed {
  margin: var(--spacing-3) var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-4);
  border: 1px solid var(--border);
  border-left: 2px solid var(--warning);
  border-radius: var(--radius-sm);
  background: var(--warning-bg);
}

/* Clamped, never cut. A summary is one sentence describing what is about to
   happen to the reader's board, so three lines is generous — but a hard
   truncation at a character count can stop mid-word and mid-meaning, and the
   reader cannot tell it happened. line-clamp keeps the ellipsis and the whole
   sentence stays in the title attribute and the expander. */
ui-chat-tool-card .chat-tool-summary {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  padding: var(--spacing-2) 0 0 18px;
  font-size: var(--fs-md);
  line-height: var(--lh-relaxed);
  color: var(--text);
  white-space: pre-wrap;
  overflow: hidden;
  overflow-wrap: anywhere;
}

ui-chat-tool-card .chat-tool-decide {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3);
  padding: var(--spacing-3) 0 var(--spacing-1) 18px;
}

ui-chat-tool-card .chat-tool-btn {
  padding: var(--spacing-2) var(--spacing-5);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  cursor: pointer;
}

/* --text-on-accent, never a hardcoded white: a brand whose accent is bright
   pairs it with near-black, and a white label there is invisible. */
ui-chat-tool-card .chat-tool-approve {
  background: var(--accent);
  color: var(--text-on-accent);
}

ui-chat-tool-card .chat-tool-reject {
  background: transparent;
  border-color: var(--border);
  color: var(--text);
}

ui-chat-tool-card .chat-tool-reject:hover {
  background: var(--surface-3);
}

ui-chat-tool-card .chat-tool-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

ui-chat-tool-card .chat-tool-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

ui-chat-tool-card .chat-tool-decide-error {
  padding: var(--spacing-2) 0 0 18px;
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--danger);
}

/* ── Chat Input ───────────────────────────────────────────── */

ui-chat-input {
  display: block;
  padding: var(--spacing-4) var(--spacing-6);
  border-top: 1px solid var(--border);
  flex-shrink: 0;
  overflow: visible;
}

ui-chat-input .chat-input-container {
  display: flex;
  align-items: flex-end;
  gap: var(--spacing-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--spacing-3) var(--spacing-4);
  transition: border-color 0.15s;
}

ui-chat-input .chat-input-container:focus-within {
  border-color: var(--accent);
}

/* THE BOX IS CENTERED, NOT THE TEXT, because a textarea's text cannot be
   vertically centered inside its own box — it is a scrollport and content
   starts at the top. The box already hugs its content (_resize sets `height`
   from scrollHeight), so centering that short box is the whole mechanism.

   The container stays `align-items: flex-end` deliberately: centering the
   textarea alone fixes the one-line case, and once it grows past the 32px send
   button the line height is its own and the button stays bottom-pinned, which
   is the conventional composer layout. */
ui-chat-input .chat-input-textarea {
  flex: 1;
  align-self: center;
  border: none;
  outline: none;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-lg);
  line-height: var(--lh-relaxed);
  resize: none;
  overflow-y: hidden;
  padding: var(--spacing-1) 0;
  min-height: 20px;
}

ui-chat-input .chat-input-textarea::placeholder {
  color: var(--text-muted);
}

ui-chat-input .chat-input-send {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--text-on-accent);
  cursor: pointer;
  flex-shrink: 0;
  transition: opacity 0.15s;
}

ui-chat-input .chat-input-send:hover {
  opacity: 0.85;
}

ui-chat-input .chat-input-send:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

ui-chat-input .chat-input-send svg {
  width: 16px;
  height: 16px;
}

ui-chat-input .chat-input-stop {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: none;
  border-radius: var(--radius);
  background: var(--danger);
  color: var(--text-on-status);
  cursor: pointer;
  flex-shrink: 0;
  transition: opacity 0.15s;
  animation: chat-stop-pulse 1.5s ease-in-out infinite;
}

ui-chat-input .chat-input-stop:hover {
  opacity: 0.85;
}

ui-chat-input .chat-input-stop svg {
  width: 14px;
  height: 14px;
}

@keyframes chat-stop-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

/* ── Skill Autocomplete Popover ── */

ui-chat-input .chat-input-wrapper {
  position: relative;
}

ui-chat-input .chat-skill-popover {
  position: absolute;
  bottom: 100%;
  left: 0;
  right: 0;
  max-height: 320px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-bottom: none;
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: var(--elev-menu-up);
  z-index: var(--z-menu);
  padding: var(--spacing-2) 0;
}

ui-chat-input .chat-skill-category {
  padding: var(--spacing-2) var(--spacing-4) var(--spacing-1);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-dim);
}

ui-chat-input .chat-skill-item {
  padding: var(--spacing-2) var(--spacing-4);
  cursor: pointer;
  transition: background 0.1s;
}

ui-chat-input .chat-skill-item:hover,
ui-chat-input .chat-skill-active {
  background: var(--accent-dim);
}

ui-chat-input .chat-skill-header {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
}

ui-chat-input .chat-skill-name {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--accent);
  font-family: var(--mono);
}

ui-chat-input .chat-skill-badge {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--info);
  background: var(--info-bg);
  padding: var(--spacing-1) 5px;
  border-radius: var(--radius-2xs);
}

ui-chat-input .chat-skill-args {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  font-style: italic;
}

ui-chat-input .chat-skill-desc {
  font-size: var(--fs-base);
  color: var(--text-muted);
  margin-top: var(--spacing-1);
  line-height: var(--lh-tight);
}

ui-chat-input .chat-skill-empty {
  padding: var(--spacing-3) var(--spacing-4);
  font-size: var(--fs-base);
  color: var(--text-dim);
  text-align: center;
}

/* ── Thinking collapsible section ── */
.chat-thinking-section {
  margin: var(--spacing-2) var(--spacing-4);
  padding: var(--spacing-2) var(--spacing-3);
  border-radius: var(--radius);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.chat-thinking-section summary {
  cursor: pointer;
  font-weight: var(--fw-medium);
  user-select: none;
  color: var(--text);
}

.chat-thinking-section .chat-thinking-content {
  margin-top: var(--spacing-2);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 300px;
  overflow-y: auto;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

/* ── Step progress indicator ── */
.chat-step-indicator {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-1) var(--spacing-3);
  margin: var(--spacing-1) var(--spacing-4);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  background: var(--surface-2);
  border-radius: var(--radius-xs);
}

.chat-step-indicator.chat-step-warning {
  color: var(--warning);
  background: var(--warning-bg);
}

/* ── Token / timing stats footer ── */
.chat-message-stats {
  display: flex;
  justify-content: flex-end;
  padding: var(--spacing-1) var(--spacing-4);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  opacity: 0.6;
}

/* ── Check List ── */
ui-check-list {
  display: block;
}

.check-list-controls {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-2) 0;
}

.check-list-count {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-weight: var(--fw-semibold);
}

.check-list-btn {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--accent);
  font-size: var(--fs-sm);
  padding: var(--spacing-1) var(--spacing-3);
  font-weight: var(--fw-medium);
}

.check-list-btn:hover {
  background: var(--surface-3, var(--surface-2));
  border-color: var(--accent);
}

.check-list-scroll {
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--spacing-2);
}

.check-list-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-1) var(--spacing-2);
  border-radius: var(--radius);
  cursor: pointer;
  font-size: var(--fs-base);
}

.check-list-row:hover {
  background: var(--surface-2);
}

.check-list-cb {
  flex-shrink: 0;
  accent-color: var(--accent);
}

.check-list-label {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--text);
  font-weight: var(--fw-medium);
  flex-shrink: 0;
}

.check-list-detail {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.check-list-empty {
  padding: var(--spacing-4);
  color: var(--text-muted);
  font-size: var(--fs-base);
  text-align: center;
}

/* ── ui-task-list — a persisted checklist ───────────────────────────
   A row in flight is dimmed but NOT hidden or collapsed: the write is the
   thing the user is waiting on, so the text they are waiting on must stay
   readable. Row controls reserve their space at all times and only change
   visibility on hover, so revealing them never reflows the list. */

ui-task-list {
  display: block;
}

ui-task-list .tkl-error {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-4);
  margin-bottom: var(--spacing-6);
  padding: var(--spacing-5) var(--spacing-6);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: var(--danger-bg);
}

ui-task-list .tkl-error-glyph {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--danger);
  margin-top: var(--spacing-1);
}

ui-task-list .tkl-error-body {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  min-width: 0;
  flex: 1;
}

ui-task-list .tkl-error-title {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  color: var(--text);
}

ui-task-list .tkl-error-detail {
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

ui-task-list .tkl-error-retry {
  flex-shrink: 0;
  padding: var(--spacing-2) var(--spacing-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
}

ui-task-list .tkl-error-retry:hover {
  border-color: var(--accent);
}

ui-task-list .tkl-progress {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  margin-bottom: var(--spacing-5);
}

ui-task-list .tkl-progress-text {
  flex-shrink: 0;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

ui-task-list .tkl-progress-track {
  flex: 1;
  height: var(--spacing-3);
  border-radius: var(--radius-full);
  background: var(--surface-3);
  overflow: hidden;
}

ui-task-list .tkl-progress-fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--accent);
}

ui-task-list .tkl-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

ui-task-list .tkl-row {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-4);
  border-radius: var(--radius-sm);
}

ui-task-list .tkl-row:hover {
  background: var(--surface-2);
}

ui-task-list .tkl-busy {
  opacity: 0.55;
}

ui-task-list .tkl-cb {
  flex-shrink: 0;
  margin: 0;
  margin-top: var(--spacing-1);
  accent-color: var(--accent);
  cursor: pointer;
}

ui-task-list .tkl-busy .tkl-cb {
  cursor: progress;
}

ui-task-list .tkl-glyph {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--text-dim);
  margin-top: var(--spacing-1);
}

ui-task-list .tkl-met .tkl-glyph {
  color: var(--success);
}

ui-task-list .tkl-text {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  color: var(--text);
  overflow-wrap: break-word;
}

ui-task-list .tkl-met .tkl-text {
  color: var(--text-muted);
  text-decoration: line-through;
}

ui-task-list .tkl-row-actions {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: var(--spacing-2);
  visibility: hidden;
}

ui-task-list .tkl-row:hover .tkl-row-actions,
ui-task-list .tkl-row:focus-within .tkl-row-actions,
ui-task-list .tkl-row-editing .tkl-row-actions {
  visibility: visible;
}

/* A hidden control cannot take focus, so the hover reveal above relies on the
   checkbox leading the tab order into the row. A row without one has no such
   lead and would strand its actions off the keyboard entirely. */
ui-task-list .tkl-row:not(:has(.tkl-cb)) .tkl-row-actions {
  visibility: visible;
}

ui-task-list .tkl-act {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--spacing-1);
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}

ui-task-list .tkl-act:hover {
  border-color: var(--border);
  background: var(--surface);
  color: var(--text);
}

ui-task-list .tkl-act-danger:hover {
  border-color: var(--danger);
  color: var(--danger);
}

ui-task-list .tkl-act[disabled] {
  cursor: not-allowed;
  color: var(--text-dim);
}

ui-task-list .tkl-field {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  flex: 1;
  min-width: 0;
}

ui-task-list .tkl-add {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  margin-top: var(--spacing-6);
}

ui-task-list .tkl-add-input,
ui-task-list .tkl-edit-input {
  flex: 1;
  min-width: 0;
  padding: var(--spacing-4) var(--spacing-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font-family: inherit;
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  outline: none;
}

ui-task-list .tkl-add-input:focus,
ui-task-list .tkl-edit-input:focus {
  border-color: var(--accent);
}

ui-task-list .tkl-counter {
  flex-shrink: 0;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

/* Text on --accent must read from the token, never a literal: a brand with a
   light accent renders hardcoded white at about 1.2:1. */
ui-task-list .tkl-add-btn {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: var(--spacing-2);
  padding: var(--spacing-4) var(--spacing-6);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--text-on-accent);
  font-family: inherit;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  cursor: pointer;
}

ui-task-list .tkl-add-btn:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

ui-task-list .tkl-empty {
  margin: 0;
  padding: var(--spacing-7) var(--spacing-4);
  color: var(--text-muted);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  text-align: center;
}

/* ══════════════════════════════════════════════════════════════════
   REDUCED MOTION (ND-35)
   Honors the OS "reduce motion" accessibility preference.

   MUST remain the last block in this file — cascade order is currently
   positional (no @layer yet; ND-95 introduces layers, after which this
   should move into a top layer instead of relying on position).

   Approach: collapse durations to ~0 rather than `animation: none`.
   Removing an animation outright strips its end state, so anything that
   animates in (opacity 0 -> 1, translateY -> 0) would be left stuck in its
   start frame. Near-zero duration still applies the final keyframe and
   still fires animationend/transitionend, so JS that awaits those keeps
   working.
   ══════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  /* ── Essential progress feedback: exempt ──
     WCAG 2.3.3 / vestibular guidance targets large-area, non-essential
     motion. These are small (12-20px), stationary, and are the only signal
     that work is in progress — freezing them mid-rotation reads as a hung
     UI rather than a calm one. They keep rotating, slowed to reduce
     stimulus. Non-essential motion (slides, shimmer, parallax, pulses) is
     NOT exempt and is collapsed by the rule above.

     ND-97 consolidates the 11 duplicate spin keyframes into one, at which
     point this list collapses to a single selector. */
  .uib-spinner,
  .spn-circle,
  .dlg-spinner,
  .ep-spinner,
  .ff-ta-spinner,
  .dt-spinner,
  .lm-spinner,
  .lm-spinner-sm,
  ui-list-view .lv-spinner,
  ui-document-viewer .dv-spinner,
  .pl-border-wrap .pl-spinner,
  ui-chat-tool-card .chat-tool-icon-running {
    animation-duration: 1.4s !important;
    animation-iteration-count: infinite !important;
  }

  /* Streaming caret: a blink conveys "still generating". Keep it, slowed.
     step-end timing means no smooth movement, so stimulus is minimal. */
  ui-chat-message .chat-cursor {
    animation-duration: 1.6s !important;
    animation-iteration-count: infinite !important;
  }
}

/* ══════════════════════════════════════════════════════════════════
   ui-query-tester — validate/preview a query without saving  (ND-264)
   ══════════════════════════════════════════════════════════════════ */

ui-query-tester {
  display: block;
}

ui-query-tester .qt-container {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
}

ui-query-tester .qt-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  flex-wrap: wrap;
}

ui-query-tester .qt-btn {
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
}

ui-query-tester .qt-btn:hover:not(:disabled) {
  background: var(--surface-3);
}

ui-query-tester .qt-btn:disabled {
  opacity: 0.5;
  cursor: default;
}

ui-query-tester .qt-btn-primary {
  color: var(--text-on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

ui-query-tester .qt-status {
  font-size: var(--fs-base);
  color: var(--text-muted);
}

ui-query-tester .qt-status.qt-success { color: var(--success); }
ui-query-tester .qt-status.qt-error   { color: var(--danger); }
ui-query-tester .qt-status.qt-warning { color: var(--warning); }

ui-query-tester .qt-warning {
  padding: var(--spacing-4) var(--spacing-6);
  font-size: var(--fs-base);
  border-radius: var(--radius-xs);
  border-left: 2px solid transparent;
}

ui-query-tester .qt-warning.qt-error {
  color: var(--danger);
  background: var(--danger-bg);
  border-left-color: var(--danger);
}

ui-query-tester .qt-warning.qt-info {
  color: var(--text-muted);
  background: var(--surface-2);
  border-left-color: var(--border);
}

ui-query-tester .qt-params,
ui-query-tester .qt-empty {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  font-family: var(--mono);
}

ui-query-tester .qt-table-wrap {
  overflow: auto;
  max-height: 320px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}

ui-query-tester .qt-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  font-family: var(--mono);
}

ui-query-tester .qt-table th {
  position: sticky;
  top: 0;
  text-align: left;
  padding: var(--spacing-3) var(--spacing-5);
  color: var(--text-muted);
  background: var(--surface-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

ui-query-tester .qt-table td {
  padding: var(--spacing-3) var(--spacing-5);
  color: var(--text);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

ui-query-tester .qt-table tbody tr:last-child td {
  border-bottom: none;
}

ui-query-tester .qt-sql {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

ui-query-tester .qt-sql summary {
  cursor: pointer;
  padding: var(--spacing-2) 0;
}

ui-query-tester .qt-sql-body {
  margin: var(--spacing-3) 0 0 0;
  padding: var(--spacing-5);
  overflow: auto;
  max-height: 240px;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  white-space: pre;
}

/* ui-report-chart — report results as a chart  (ND-267) */

ui-report-chart {
  display: block;
}

/* Supplied mode only. A composition with no chart renders nothing, and nothing
   must occupy no space — an empty block still takes a flex/grid gap from its
   neighbors, which is a trace. Unscoped this would also change the computed
   display of a fetching chart before its first row arrives. */
ui-report-chart[data-path]:empty {
  display: none;
}

ui-report-chart .rc-message {
  margin: 0;
  font-size: var(--fs-base);
  color: var(--text-muted);
  /* These messages carry AUTHOR-CONTROLLED text — a dot-path from
     pdf-config-path, a JSON parser's complaint, an RPC error — and a single
     unbreakable token measured 1030px in a 394px panel, painting 607px into the
     neighboring one. Same provenance argument as .ff-lookup-status (ND-761). */
  overflow-wrap: anywhere;
}

/* A report that declares an UNREADABLE chart must not look like a report that
   declares none. The empty state is muted advice; this is a defect report. */
ui-report-chart .rc-message.rc-error {
  color: var(--danger);
}

/* ══════════════════════════════════════════════════════════════════
   ui-report-builder — design a report by picking fields  (ND-267)

   A three-zone workbench under one editable sentence:

     ┌── strip: the whole report as words, plus Save ──────────────┐
     │ fields (280) │ canvas — table + chart │ settings (300, ⇔)  │
     └─────────────────────────────────────────────────────────────┘

   It was previously two panes — a 400px stack of six collapsible
   sections beside the results — which put every control in one
   scrolling column and left the canvas too narrow to show a wide
   table or a chart at all. Picking fields, reading the outcome and
   configuring the output are three different activities, so they get
   three places that do not move.
   ══════════════════════════════════════════════════════════════════ */

ui-report-builder {
  display: block;
  container-type: inline-size;
}

ui-report-builder .rb-workbench {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
  min-width: 0;
}

/* ── The definition strip ── */

ui-report-builder .rb-strip {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--spacing-7);
  padding: var(--spacing-5) var(--spacing-6);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

ui-report-builder .rb-sentence {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  flex-wrap: wrap;
  min-width: 0;
}

ui-report-builder .rb-seg {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  flex-wrap: wrap;
}

ui-report-builder .rb-seg-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
}

ui-report-builder .rb-seg-empty {
  font-size: var(--fs-base);
  font-style: italic;
  color: var(--text-dim);
}

ui-report-builder .rb-strip-actions {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  flex-shrink: 0;
}

/* ── Chips ── */

ui-report-builder .rb-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  max-width: 240px;
  padding: var(--spacing-2) var(--spacing-3) var(--spacing-2) var(--spacing-4);
  font-size: var(--fs-base);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
}

ui-report-builder .rb-chip[draggable="true"] { cursor: grab; }
ui-report-builder .rb-chip-dragging { opacity: 0.4; }

ui-report-builder .rb-chip-drop {
  border-color: var(--accent);
  background: var(--accent-dim);
}

ui-report-builder .rb-chip-draft {
  border-style: dashed;
  color: var(--text-muted);
}

ui-report-builder .rb-chip-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: inherit;
  font-family: inherit;
  font-size: var(--fs-base);
  background: none;
  border: none;
  padding: 0;
}

ui-report-builder button.rb-chip-text { cursor: pointer; }
ui-report-builder button.rb-chip-text:hover { color: var(--accent); }

ui-report-builder .rb-chip-x {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  padding: 0;
  font-size: var(--fs-md);
  line-height: var(--lh-none);
  color: var(--text-dim);
  background: none;
  border: none;
  border-radius: var(--radius-circle);
  cursor: pointer;
}

ui-report-builder .rb-chip-x:hover {
  color: var(--danger);
  background: var(--danger-bg);
}

ui-report-builder .rb-chip-add {
  padding: var(--spacing-2) var(--spacing-5);
  color: var(--text-muted);
  background: none;
  border-style: dashed;
  cursor: pointer;
}

ui-report-builder .rb-chip-add:hover {
  color: var(--accent);
  border-color: var(--accent);
  border-style: solid;
}

ui-report-builder .rb-select-dataset { font-weight: var(--fw-semibold); }

/* ── Three zones ──
   min-height rather than a stretched height: the builder sits inside a
   scrolling panel body, so there is no fixed height to divide. When the
   panel is filling the window there is, and the canvas should take it. */

ui-report-builder .rb-body {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr) auto;
  gap: var(--spacing-6);
  align-items: stretch;
  min-height: 520px;
}

ui-content-panel[data-expanded] ui-report-builder .rb-body {
  min-height: calc(100vh - 320px);
}

@container (max-width: 1100px) {
  ui-report-builder .rb-body { grid-template-columns: 240px minmax(0, 1fr); }
  ui-report-builder .rb-rail-settings { grid-column: 1 / -1; }
}

@container (max-width: 720px) {
  ui-report-builder .rb-body { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  ui-report-builder .rb-rail-fields { grid-column: 1 / -1; }
}

ui-report-builder .rb-rail {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

ui-report-builder .rb-rail-body {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
  padding: var(--spacing-5);
  overflow: auto;
  flex: 1;
}

/* ── Fields rail ── */

ui-report-builder .rb-search {
  display: flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-4) var(--spacing-5);
  border-bottom: 1px solid var(--border);
}

ui-report-builder .rb-search-input {
  flex: 1;
  min-width: 0;
}

ui-report-builder .rb-search-key {
  padding: var(--spacing-1) var(--spacing-3);
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: var(--radius-2xs);
}

ui-report-builder .rb-group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-3) var(--spacing-2);
}

ui-report-builder .rb-group-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

ui-report-builder .rb-group-count {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-dim);
}

/* ── Settings rail ── */

ui-report-builder .rb-rail-settings { width: 300px; }
ui-report-builder .rb-rail-settings.rb-rail-shut { width: auto; }

ui-report-builder .rb-rail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--spacing-5);
  border-bottom: 1px solid var(--border);
}

ui-report-builder .rb-rail-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

ui-report-builder .rb-rail-toggle {
  padding: 0 var(--spacing-3);
  font-size: var(--fs-lg);
  color: var(--text-muted);
  background: none;
  border: none;
  cursor: pointer;
}

ui-report-builder .rb-rail-toggle:hover { color: var(--text); }

/* ── Canvas ── */

ui-report-builder .rb-canvas {
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}

ui-report-builder .rb-canvas-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-6);
  padding: var(--spacing-4) var(--spacing-5);
  border-bottom: 1px solid var(--border);
}

ui-report-builder .rb-canvas-bar-right {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  min-width: 0;
}

ui-report-builder .rb-canvas-body {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
  padding: var(--spacing-6);
  overflow: auto;
  flex: 1;
}

ui-report-builder .rb-canvas-chart { min-width: 0; }

/* ── The PDF view ──
   The document fills the canvas, exactly where the chart and table were.
   There is no Generate button: choosing the PDF tab is the request, and a
   separate control to fulfill a request already made is a step too many. */

/* Anchors carrying .rb-btn need the box behavior a button gets for free. */
ui-report-builder a.rb-btn {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

ui-report-builder .rb-canvas-pdf {
  display: flex;
  flex: 1;
  min-height: 480px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  overflow: hidden;
}

ui-report-builder .rb-canvas-pdf ui-document-viewer {
  flex: 1;
  min-width: 0;
}

/* Says the document no longer matches the design, and fixes it in one click.
   Not an auto-regenerate: every generate writes a run record and costs a
   round trip, so doing it on each keystroke would be both noisy and slow. */

ui-report-builder .rb-stale {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  flex-wrap: wrap;
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--warning-bg);
  border: 1px solid var(--warning);
  border-radius: var(--radius-xs);
}

ui-report-builder .rb-stale-text {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-base);
  color: var(--text);
}

ui-report-builder .rb-switch {
  display: inline-flex;
  padding: var(--spacing-1);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}

ui-report-builder .rb-switch-btn {
  padding: var(--spacing-2) var(--spacing-6);
  font-size: var(--fs-base);
  color: var(--text-muted);
  background: none;
  border: none;
  border-radius: var(--radius-2xs);
  cursor: pointer;
  white-space: nowrap;
}

ui-report-builder .rb-switch-btn:hover { color: var(--text); }

ui-report-builder .rb-switch-on {
  color: var(--text);
  background: var(--surface-3);
}

/* ── Sections ── */

ui-report-builder .rb-section {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

ui-report-builder .rb-section-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  width: 100%;
  padding: var(--spacing-5) var(--spacing-6);
  color: var(--text);
  background: var(--surface-2);
  border: none;
  cursor: pointer;
  text-align: left;
}

ui-report-builder .rb-section-head:hover { background: var(--surface-3); }

ui-report-builder .rb-caret {
  color: var(--text-muted);
  font-size: var(--fs-xs);
}

ui-report-builder .rb-section-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
}

ui-report-builder .rb-section-body {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-5);
  padding: var(--spacing-6);
}

/* `display: flex` above beats the UA stylesheet's [hidden] { display: none },
   so every collapsed section rendered its body while its caret said otherwise —
   collapsing had never worked. An author-level rule is required to win. */

ui-report-builder .rb-section-body[hidden] { display: none; }

ui-report-builder .rb-hint {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-normal);
}

/* ── Field pickers ── */

ui-report-builder .rb-field-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
}

ui-report-builder .rb-field {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-4);
  padding: var(--spacing-3) var(--spacing-4);
  border-radius: var(--radius-2xs);
  cursor: pointer;
}

ui-report-builder .rb-field:hover { background: var(--surface-2); }
ui-report-builder .rb-field-on { background: var(--surface-2); }

ui-report-builder .rb-field input[type="checkbox"] { margin-top: var(--spacing-1); }

ui-report-builder .rb-field-text {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  min-width: 0;
}

ui-report-builder .rb-field-top {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}

ui-report-builder .rb-field-label {
  font-size: var(--fs-base);
  color: var(--text);
}

ui-report-builder .rb-field-desc {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-snug);
}

ui-report-builder .rb-field-agg {
  padding: 0 var(--spacing-3);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  background: var(--surface-3);
  border-radius: var(--radius-full);
}

/* ── Controls ── */

ui-report-builder .rb-filter-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  flex-wrap: wrap;
}

ui-report-builder .rb-filter-value {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  flex-wrap: wrap;
  flex: 1;
  min-width: 140px;
}

ui-report-builder .rb-inline-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  white-space: nowrap;
}

/* A label and its control travel together, so a narrow rail can only ever
   wrap between pairs — never between a label and the thing it names. */

ui-report-builder .rb-pair {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  /* Wrap to the next line rather than squeeze: three pairs sharing a 270px rail
     shrank the sort select until its value read "Submissio". */
  min-width: 110px;
  flex: 1;
}

ui-report-builder .rb-select,
ui-report-builder .rb-input {
  padding: var(--spacing-3) var(--spacing-4);
  font-size: var(--fs-base);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  max-width: 100%;
}

ui-report-builder .rb-select-grain { margin: 0 0 var(--spacing-2) var(--spacing-9); }
ui-report-builder .rb-input-narrow { width: 96px; }
ui-report-builder .rb-input-wide { flex: 1; min-width: 160px; }

/* ── Value picker ── */

ui-report-builder .rb-value-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  width: 100%;
  max-height: 200px;
  overflow: auto;
  padding: var(--spacing-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}

ui-report-builder .rb-value {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-2) var(--spacing-3);
  font-size: var(--fs-base);
  border-radius: var(--radius-2xs);
  cursor: pointer;
}

ui-report-builder .rb-value:hover { background: var(--surface-2); }
ui-report-builder .rb-value-on { background: var(--surface-3); }
ui-report-builder .rb-value-text { flex: 1; color: var(--text); }

ui-report-builder .rb-value-count {
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  color: var(--text-muted);
}

/* ── Buttons ── */

ui-report-builder .rb-btn {
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
}

ui-report-builder .rb-btn:hover:not(:disabled) { background: var(--surface-3); }
ui-report-builder .rb-btn:disabled { opacity: 0.5; cursor: default; }

ui-report-builder .rb-btn-primary {
  color: var(--text-on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

ui-report-builder .rb-btn-quiet {
  align-self: flex-start;
  font-size: var(--fs-sm);
}

ui-report-builder .rb-btn-icon {
  padding: var(--spacing-2) var(--spacing-4);
  color: var(--text-muted);
}

/* ── Status and warnings ── */

ui-report-builder .rb-status { font-size: var(--fs-sm); color: var(--text-muted); }
ui-report-builder .rb-status.rb-success { color: var(--success); }
ui-report-builder .rb-status.rb-error { color: var(--danger); }
ui-report-builder .rb-status.rb-busy { color: var(--accent); }

ui-report-builder .rb-warning {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
  align-items: flex-start;
  padding: var(--spacing-4) var(--spacing-6);
  font-size: var(--fs-base);
  border-radius: var(--radius-xs);
  border-left: 2px solid transparent;
}

ui-report-builder .rb-warning.rb-error {
  color: var(--danger);
  background: var(--danger-bg);
  border-left-color: var(--danger);
}

ui-report-builder .rb-warning.rb-info {
  color: var(--text-muted);
  background: var(--surface-2);
  border-left-color: var(--border);
}

/* ── Results pane ── */

/* An empty state is a place to act, not a place to be told something —
   so it is a card with a title, a sentence and the button that does the
   thing the sentence describes. */

ui-report-builder .rb-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--spacing-5);
  margin: auto;
  padding: var(--spacing-10) var(--spacing-8);
  max-width: 420px;
  font-size: var(--fs-base);
  color: var(--text-muted);
  text-align: center;
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
}

ui-report-builder .rb-empty-title {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-report-builder .rb-empty-text { line-height: var(--lh-relaxed); }

ui-report-builder .rb-empty-sm {
  margin: 0;
  padding: var(--spacing-7) var(--spacing-5);
  max-width: none;
  font-size: var(--fs-sm);
  line-height: var(--lh-relaxed);
}

/* ── The SQL view ──
   Was a <details> in the toolbar. It opened as a popover that overflowed the
   canvas and covered the settings rail, and a twisty implied a debugging aid
   rather than the report's actual output. It is a view like the others. */

ui-report-builder .rb-sql-view {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-5);
  min-width: 0;
}

ui-report-builder .rb-sql-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-6);
  flex-wrap: wrap;
}

ui-report-builder .rb-sql-title {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

ui-report-builder .rb-sql-body {
  margin: 0;
  padding: var(--spacing-7);
  overflow: auto;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  font-family: var(--mono);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
  white-space: pre;
  user-select: text;
}

/* Values are listed, never substituted into the text above. That is the point:
   the SQL you are reading is the SQL that runs, and filter values reach it as
   bound parameters rather than concatenated literals. */

ui-report-builder .rb-sql-params {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  padding: var(--spacing-6);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
}

ui-report-builder .rb-sql-params-title {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}

ui-report-builder .rb-sql-param-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}

ui-report-builder .rb-sql-param {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-6);
  font-family: var(--mono);
  font-size: var(--fs-sm);
}

ui-report-builder .rb-sql-param-key { color: var(--accent); }
ui-report-builder .rb-sql-param-val { color: var(--text); }

ui-report-builder .rb-table-wrap {
  overflow: auto;
  max-height: 520px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

ui-report-builder .rb-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

ui-report-builder .rb-table th {
  position: sticky;
  top: 0;
  text-align: left;
  padding: var(--spacing-4) var(--spacing-5);
  color: var(--text-muted);
  background: var(--surface-3);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  font-weight: var(--fw-semibold);
}

ui-report-builder .rb-table td {
  padding: var(--spacing-3) var(--spacing-5);
  color: var(--text);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  font-family: var(--mono);
}

ui-report-builder .rb-table tbody tr:last-child td { border-bottom: none; }
ui-report-builder .rb-table tbody tr:hover td { background: var(--surface-2); }

/* ══════════════════════════════════════════════════════════════════
   Fill-window panels — <ui-node-header expandable>

   A CSS overlay rather than the Fullscreen API, so the app's theme and
   its toast/dropdown layers keep working. Sits in the --z-modal band,
   which is ordered BELOW dropdown/popover/toast/tooltip — so every
   control inside an expanded panel still layers above it correctly.
   ══════════════════════════════════════════════════════════════════ */

ui-content-panel[data-expanded],
ui-tab-panel[data-expanded] {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  flex-direction: column;
  overflow: auto;
  padding: var(--spacing-7);
  background: var(--bg);
  /* Not `display: block` when hidden elsewhere — an expanded panel is always the
     active one, so it does not have to fight the panel-visibility rules. */
}

/* Keep the header pinned while the body scrolls: the exit control must stay
   reachable, otherwise a long report traps you until you remember Esc. */
ui-content-panel[data-expanded] > ui-node-header,
ui-tab-panel[data-expanded] > ui-node-header {
  position: sticky;
  top: calc(var(--spacing-7) * -1);
  z-index: var(--z-sticky);
  padding-top: var(--spacing-4);
  padding-bottom: var(--spacing-4);
  background: var(--bg);
}

/* Suppress scroll behind the overlay. */
html[data-panel-expanded] {
  overflow: hidden;
}

ui-node-header .nh-expand {
  margin-left: var(--spacing-3);
}

/* ══════════════════════════════════════════════════════════════════
   <ui-spotlight> — viewport scrim with a hole cut in it (.sp-*)

   The layer is mounted on <body> by the element, not nested inside it, because
   `position: fixed` resolves against a transformed ancestor rather than the
   viewport. See the element's header comment.

   POINTER-EVENTS IS THE LOAD-BEARING DECLARATION HERE. The caller that raises a
   spotlight goes on to click the thing it is pointing at, so the layer must be
   transparent to hit-testing. It is set on the layer AND on its children, so a
   future child cannot reintroduce the problem by carrying its own value.
   ══════════════════════════════════════════════════════════════════ */

/* The host itself renders nothing and takes no space — everything it draws
   lives in the body-mounted layer. */
ui-spotlight {
  display: none;
}

.sp-layer {
  position: fixed;
  inset: 0;
  z-index: var(--z-spotlight);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 160ms ease;
}

.sp-layer > * {
  pointer-events: none;
}

.sp-layer.sp-on {
  opacity: 1;
  visibility: visible;
}

/* Four of these tile the viewport around the hole. Each is positioned in px by
   the element from a measured rect. */
.sp-mask {
  position: fixed;
  background: var(--scrim);
}

/* Traces the hole so the eye lands on an edge rather than on an absence. */
.sp-ring {
  position: fixed;
  border: 2px solid var(--accent);
  border-radius: var(--radius);
  animation: sp-pulse 2s ease-in-out infinite;
}

/* The one generic attention animation in this stylesheet. The global
   prefers-reduced-motion rule collapses it along with every other non-essential
   pulse, so it needs no exemption of its own. */
@keyframes sp-pulse {
  0%,
  100% {
    box-shadow: 0 0 0 var(--spacing-1) var(--accent-dim);
  }
  50% {
    box-shadow: 0 0 0 var(--spacing-6) var(--accent-dim);
  }
}

.sp-caption {
  position: fixed;
  display: none;
  max-width: 44ch;
  /* A caption is a string a caller passes in, so a URL or an identifier with no
     break opportunity is the ORDINARY case, not a contrived one. Without this a
     120-character token renders as one unbroken line straight off the viewport,
     and the JS clamp cannot save it because the box is already wider than the
     screen. Same fix, same reason, as .ff-lookup-status in ND-763. */
  overflow-wrap: anywhere;
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--elev-popover);
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
  line-height: var(--lh-normal);
}

.sp-caption.sp-caption-on {
  display: block;
}

/* ══════════════════════════════════════════════════════════════════
   <ui-demo-panel> — docked, non-modal narration panel (.dp-*)

   NO BACKDROP RULE EXISTS HERE, and that is deliberate rather than an omission:
   the panel is not modal, the page behind it keeps scrolling, and the element
   never writes document.body.style.overflow. A scrim added here would be the
   first half of turning this back into a drawer.

   THE PANEL RESERVES ITS OWN COLUMN, and that IS a rule here — the one below.
   A fixed column takes no layout space, so without it the application laid out
   under the full viewport width and the panel covered its right-hand 360px:
   measured at 1440px wide, elementFromPoint() over a right-edge primary button
   returned the panel, and a ui-spotlight ring on that button ran 336px across
   it. Padding the ROOT element is what fixes it; the element publishes
   `data-demo-panel` while it is open. See the element's header comment for why
   the root and not a shell container, and why physical rather than logical.
   ══════════════════════════════════════════════════════════════════ */

/* One width, read by the dock and by the reservation, so the two cannot drift
   apart and leave a strip of application uncovered or a strip of padding with
   no panel in it. min() rather than a separate max-width for the same reason:
   on a narrow viewport the panel is 92vw and the reservation must be 92vw too. */
:root {
  --demo-panel-width: min(360px, 92vw);
}

/* Keep the application out from under the panel. `~=` because the value is a
   list of docked sides: two panels may be open, one on each edge. */
html[data-demo-panel~='right'] {
  padding-right: var(--demo-panel-width);
}

html[data-demo-panel~='left'] {
  padding-left: var(--demo-panel-width);
}

/* THE ONE THING PADDING CANNOT MOVE, AND IT IS THE ONE THAT MATTERED.
   ui-toast is `position: fixed; top: 16px; right: 16px`, so it is anchored to
   the viewport and a reservation on the root element does not reach it: the
   application vacated the panel's column and the toast stayed behind in it.
   Measured by browser-verifier on the live shell at 1257px wide — a toast
   overlapped the open panel by 253px and covered its heading AND its close
   control. Only visual (the toast region is pointer-events: none), and only
   while a demo panel is open, but "only visual" is the whole cost on a stage.

   A translate rather than a new `right`, so the 16px gap is not restated here
   and cannot drift from the rule above. It does NOT touch the z-scale: a toast
   still paints over the panel (--z-toast 1500 > --z-demo-panel 1425), which is
   the recorded decision — the application must be able to speak over a demo
   overlay. This only stops it having to. A left-docked panel needs no rule:
   the toast region is top-RIGHT and never reaches it. */
html[data-demo-panel~='right'] ui-toast {
  transform: translateX(calc(var(--demo-panel-width) * -1));
}

ui-demo-panel {
  position: fixed;
  top: 0;
  bottom: 0;
  z-index: var(--z-demo-panel);
  display: flex;
  flex-direction: column;
  width: var(--demo-panel-width);
  background: var(--surface);
  border-left: 1px solid var(--border);
  box-shadow: var(--elev-overlay);
  transition: transform 200ms ease;
}

ui-demo-panel.dp-right {
  right: 0;
  transform: translateX(100%);
}

ui-demo-panel.dp-left {
  left: 0;
  border-left: none;
  border-right: 1px solid var(--border);
  transform: translateX(-100%);
}

ui-demo-panel[open] {
  transform: translateX(0);
}

/* A closed panel is parked off-screen. It must not intercept a click on its way
   out or back, and it must not be reachable by Tab while it is invisible. */
ui-demo-panel:not([open]) {
  pointer-events: none;
  visibility: hidden;
}

.dp-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-4);
  padding: var(--spacing-6) var(--spacing-7);
  border-bottom: 1px solid var(--border);
}

/* ── The audience register ──
   Sized to be read from across a room, so it is the only thing in the panel
   using the display sizes. */
.dp-beat {
  flex: 0 0 auto;
  /* min-width:0 because a flex item defaults to min-width:auto, and that is what
     lets one unbreakable token in the beat text widen the whole panel and paint
     over the page behind it — measured at 177px of overflow and 161px of ink
     outside the panel, and worst on side="left", where it lands on the page
     rather than off the viewport edge. */
  min-width: 0;
  padding: var(--spacing-8) var(--spacing-7);
  border-bottom: 1px solid var(--border);
}

.dp-beat-title {
  overflow-wrap: anywhere;
  margin-bottom: var(--spacing-4);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  color: var(--text);
}

.dp-beat-text {
  overflow-wrap: anywhere;
  font-size: var(--fs-xl);
  font-weight: var(--fw-normal);
  line-height: var(--lh-relaxed);
  color: var(--text);
}

.dp-beat-idle {
  font-size: var(--fs-md);
}

/* ── The presenter register ──
   Everything here is deliberately quiet: it is for the person talking, not for
   the room, and it scrolls independently so a long catalog cannot push the beat
   off the top. */
.dp-presenter {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  overflow-y: auto;
  padding: var(--spacing-6) var(--spacing-7);
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
}

.dp-progress {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
}

.dp-progress-label {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

.dp-progress-track {
  flex: 1 1 auto;
  height: var(--spacing-2);
  border-radius: var(--radius-full);
  background: var(--surface-3);
  overflow: hidden;
}

.dp-progress-fill {
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--accent);
  transition: width 200ms ease;
}

.dp-status {
  display: flex;
}

.dp-catalog {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

/* A native button: it carries a label and a description on two lines, which is
   more than ui-button models, and it is keyboard-operable with no help. */
.dp-scenario {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  width: 100%;
  min-width: 0;
  padding: var(--spacing-5) var(--spacing-6);
  text-align: left;
  cursor: pointer;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  font-family: inherit;
}

.dp-scenario:hover {
  background: var(--surface-3);
  border-color: var(--accent);
}

.dp-scenario-label {
  overflow-wrap: anywhere;
  font-size: var(--fs-md);
  font-weight: var(--fw-medium);
}

.dp-scenario-desc {
  overflow-wrap: anywhere;
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

/* ─────────────────────────────────────────────────────────────────────────────
   ui-destructive-confirm — irreversible action, shown before agreed

   Sits at --z-modal like any dialog: it IS the modal, not something layered over
   one, and stacking it above --z-demo-panel or --z-spotlight would let it cover
   an overlay whose whole job is to be on top.
   ───────────────────────────────────────────────────────────────────────────── */
ui-destructive-confirm:not([open]) {
  display: none;
}
.dc-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  z-index: var(--z-modal);
}
.dc-box {
  position: fixed;
  z-index: calc(var(--z-modal) + 1);
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  width: min(560px, 92vw);
  max-height: 82vh;
  background: var(--surface);
  border: 1px solid var(--danger);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-modal);
}
.dc-head {
  padding: var(--spacing-7);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
  color: var(--danger);
}
.dc-body {
  padding: var(--spacing-7);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
  min-width: 0;
}
.dc-label {
  font-size: var(--fs-xl);
  font-weight: var(--fw-semibold);
  color: var(--text);
}
.dc-summary {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.dc-section-title,
.dc-blocked-title,
.dc-warn-title {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-dim);
  margin-bottom: var(--spacing-4);
}
.dc-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
  table-layout: fixed;
}
.dc-table td {
  padding: var(--spacing-4) var(--spacing-8);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  overflow-wrap: anywhere;
}
.dc-table td:last-child {
  text-align: right;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.dc-blocked,
.dc-warn,
.dc-error {
  padding: var(--spacing-6);
  border-radius: var(--radius-md);
  font-size: var(--fs-sm);
  overflow-wrap: anywhere;
  min-width: 0;
}
.dc-blocked,
.dc-error {
  background: var(--danger-bg);
  color: var(--danger);
}
.dc-warn {
  background: var(--warning-bg);
  color: var(--warning);
}
.dc-blocked-list,
.dc-warn-list {
  margin: 0;
  padding-left: var(--spacing-7);
}
.dc-foot {
  display: flex;
  align-items: center;
  gap: var(--spacing-8);
  flex-wrap: wrap;
  padding: var(--spacing-7);
  border-top: 1px solid var(--border);
}
/* THE PROMPT TAKES A WHOLE ROW, and the buttons share the next one.
   At `flex: 1 1 auto` it grew to 498px of a 558px footer — because it contains
   the identifier box, which is itself `flex: 1 1 12ch` — so the two buttons could
   not fit beside it and `flex-wrap` put them on SEPARATE rows: Cancel alone at the
   right, then the confirm button alone at the left, hard against the bottom border.
   Measured, not guessed: three rows for three children. Claiming the full row
   makes the wrap deliberate instead of incidental, and `margin-left: auto` on the
   first button right-aligns the pair the way every other footer here does. It is
   also correct in the cancel-only states (idle, blocked, failed), where Cancel is
   the single child. */
.dc-prompt {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  flex: 1 1 100%;
  min-width: 0;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.dc-foot .dc-cancel {
  margin-left: auto;
}
.dc-code {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--text);
  background: var(--surface-2);
  padding: 0 var(--spacing-4);
  border-radius: var(--radius-sm);
}
/* SIZED LIKE EVERY OTHER TEXT INPUT IN THE SDK, because it is one: .ff-input's
   34px box and `0 var(--spacing-5)` padding. It previously carried
   `padding: var(--spacing-8)` — 24px on every side against 11px text — which
   rendered a 62px-tall control, and `flex: 1 1 12ch` then let it grow to ~498px
   of a 558px footer. An identifier is ~8 characters, so a greedy box is wide
   dead space AND the reason the buttons had nowhere to sit. `0 1 20ch` gives it
   room to show a long token without claiming the whole row. */
.dc-token {
  flex: 0 1 20ch;
  min-width: 0;
  height: 34px;
  padding: 0 var(--spacing-5);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
/* --spacing-8 is 24px, which against --fs-sm text produced a 62px-tall,
   near-square button. This is .uib-sm's step, the scale's own answer for a
   small control. */
.dc-btn {
  padding: var(--spacing-3) var(--spacing-6);
  border-radius: var(--radius-md);
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  cursor: pointer;
}
.dc-go {
  border-color: var(--danger);
  background: var(--danger);
  color: var(--text-on-accent);
}
.dc-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.dc-wait {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
/* The never-opened state. Deliberately NOT --danger: an idle dialog reports that
   nothing has been asked, and painting that in the destructive color reads as a
   refusal. Shares .dc-wait's treatment because both are "no answer yet". */
.dc-idle {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}

/* ─── ui-demo-panel: narration controls ───────────────────────────────────────
   Presenter-facing, so quiet: same register as .dp-transport, not the beat. */
.dp-speech {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  padding-top: var(--spacing-8);
  border-top: 1px solid var(--border);
}
.dp-voice {
  width: 100%;
  min-width: 0;
  padding: var(--spacing-4) var(--spacing-8);
  font-size: var(--fs-xs);
  color: var(--text);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.dp-rate-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-8);
  min-width: 0;
}
.dp-rate {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--accent);
}
.dp-rate-label {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* ─── ui-demo-panel: the unattended kiosk transport ───────────────────────────
   PRESENTER REGISTER, and on a trade-show monitor not even that: the salesperson
   starts the loop and walks away, so nobody in front of the screen ever sees this.
   Same treatment as .dp-speech — quiet, small, separated by a rule — because it is
   the same kind of thing: setup, not narration. Nothing here borrows the beat's
   sizing, deliberately. */
.dp-kiosk {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  padding-top: var(--spacing-8);
  border-top: 1px solid var(--border);
}
.dp-kiosk-state {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--spacing-4);
  min-width: 0;
}
.dp-kiosk-cycle {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
/* The scenario name comes from the host and can be any length, so it wraps rather
   than widening the panel — the .dp-beat overflow lesson, same fix. */
.dp-kiosk-scenario {
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.dp-kiosk-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  flex-wrap: wrap;
  min-width: 0;
}
.dp-speed-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-8);
  min-width: 0;
}
.dp-speed-caption {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
.dp-speed {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--accent);
}
.dp-speed-label {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* ─── ui-demo-stage — audience surface, ~3px of chrome ────────────────────────
   Sits above the spotlight scrim: the caption explains what the scrim is
   pointing at, so being dimmed by it would be self-defeating. Below --z-toast,
   which is still the top of the world. */
ui-demo-stage {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-spotlight) + 1);
  /* The driver clicks the very elements this sits beside. A caption that took a
     pointer event would break the demo it narrates. */
  pointer-events: none;
}
ui-demo-stage:not([active]) {
  display: none;
}

.ds-edge {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--border);
}
.ds-edge-fill {
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width var(--transition-slow, 400ms) ease-out;
}
.ds-edge[data-phase='paused'] .ds-edge-fill { background: var(--warning); }
.ds-edge[data-phase='failed'] .ds-edge-fill { background: var(--danger); }
.ds-edge[data-phase='complete'] .ds-edge-fill { background: var(--success); }

/* Something is in flight. Dead air during a five-second rate call is the moment a
   room decides the software is broken; a moving edge says it is working. */
.ds-edge-busy .ds-edge-fill {
  background-image: linear-gradient(90deg,
    transparent 0%, rgba(255, 255, 255, 0.55) 50%, transparent 100%);
  background-size: 200% 100%;
  animation: ds-edge-shimmer 1.1s linear infinite;
}
@keyframes ds-edge-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}

/* Deliberately NOT a box: a scrim and words. Type is larger than feels right on
   a local monitor because Zoom recompresses it and thin text smears. */
.ds-caption {
  position: absolute;
  max-width: 44ch;
  padding: var(--spacing-6) var(--spacing-7);
  border-radius: var(--radius-md);
  /* DEFINITION IS NOT CHROME, and the first version confused the two. "No chrome"
     means no title bar, no close button, no controls — it does NOT mean no
     boundary. On --surface over a light page this washed into the background and
     read as loose text floating over the kanban behind it.
     Three things separate it now: a raised surface, a real border, and an accent
     left edge. The edge does double duty — it is a signature, so the room reads
     the card as the demo speaking rather than as part of the product. */
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  background: var(--surface-elevated);
  box-shadow: var(--shadow-lg);
  /* Over dense content — a kanban board, a rating worksheet — an opaque card alone
     still competes. Blurring what is behind buys separation no border can. */
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: ds-caption-in 180ms ease-out;
}
/* CENTERD, not bottom-left. Bottom-left is where the pill now lives in the sidebar
   footer, and the two overlapped. Centering is also the right idiom for a caption a
   room is reading — it is where subtitles go — and it cannot collide with a left
   footer or a right drawer.
   Auto margins rather than translateX: .ds-caption's entry animation owns
   `transform`, and a second transform here would fight it. */
.ds-caption-lower {
  top: auto;
  bottom: var(--spacing-9);
  left: 0;
  right: 0;
  margin-inline: auto;
  width: fit-content;
}
.ds-caption-title {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  /* Accent, matching the left edge: title and edge together are the signature
     that says the demo is speaking, not the product. */
  color: var(--accent);
  margin-bottom: var(--spacing-4);
}
/* --fs-3xl (20px), not --fs-lg (14px). This scale is tuned for dense underwriting
   screens read at arm's length; a caption is read by a room, through Zoom's
   recompression, on a window that has been scaled down at the far end. 14px
   measured legible locally and is not legible there. */
.ds-caption-text {
  font-size: var(--fs-3xl);
  line-height: var(--lh-relaxed);
  color: var(--text);
}
.ds-caption-hint {
  margin-top: var(--spacing-8);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}
@keyframes ds-caption-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ─── ui-demo-stage: the act ribbon ───────────────────────────────────────────
   "Act 3 of 6 · Rating" — the only orientation an unattended viewer gets, so it
   is sized for a stranger standing three meters away rather than for a presenter
   at a desk. --fs-xl (16px) is 60% larger than the presenter register (--fs-xs,
   10px) and deliberately smaller than the caption's --fs-3xl (20px): the caption
   is the sentence being read, this is the map beside it.

   Top-LEFT, under the 3px progress edge, and the caption is kept out of the strip
   underneath it by _topKeepOut() in the element — which MEASURES this box rather
   than repeating its height as a constant here. It carries the same accent left
   edge as the caption and the pill, because all three are the demo speaking and
   none of them are the product. */
.ds-act {
  position: absolute;
  top: var(--spacing-7);
  left: var(--spacing-7);
  display: flex;
  align-items: baseline;
  gap: var(--spacing-4);
  max-width: 44ch;
  padding: var(--spacing-4) var(--spacing-7);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
  box-shadow: var(--elev-card);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  font-size: var(--fs-xl);
  line-height: var(--lh-tight);
}
.ds-act-index {
  flex: 0 0 auto;
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  /* --text, not --accent: this has to survive a carrier whose accent is a pale
     lime, and the accent signature is already carried by the left edge. */
  color: var(--text);
}
.ds-act-sep {
  flex: 0 0 auto;
  color: var(--text-dim);
}
.ds-act-label {
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: var(--fw-medium);
  color: var(--text-muted);
}

/* ─── ui-demo-stage: the between-runs recap poster ────────────────────────────
   THE MOST-VIEWED SCREEN OF THE WHOLE SHOW. On an unattended monitor a cycle runs
   for minutes and this sits between cycles, so a passer-by is far more likely to
   meet this than any part of the demo — it is composed as a poster, not as a
   dialog: centerd, generous, nothing to press, and the figures the run produced
   are the largest thing on it.

   Its type is NOT on the --fs-* scale and that is deliberate rather than
   overlooked. That scale is tuned for dense underwriting screens read at arm's
   length and tops out at 28px; this is read at three meters on a monitor that may
   be 55 inches, so the sizes are viewport-relative and clamped, with the lower
   bound of each one landing on the scale step it would otherwise use. Declared as
   tokens here for the same reason --demo-panel-width is: one name, read in one
   place, so a rebrand or a retune has a single site. */
:root {
  --ds-recap-title: clamp(28px, 4.6vw, 76px);
  --ds-recap-subtitle: clamp(16px, 1.7vw, 30px);
  --ds-recap-fact-value: clamp(24px, 3.2vw, 56px);
  --ds-recap-fact-label: clamp(11px, 1vw, 18px);
  --ds-recap-footer: clamp(13px, 1.3vw, 24px);
}

.ds-recap {
  position: absolute;
  inset: 0;
  /* LOCAL band: it never leaves this element's stacking context, it only has to
     clear the caption and the edge it covers. */
  z-index: var(--z-raised);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-9);
  padding: var(--spacing-10);
  /* Fully opaque, not a scrim: between cycles the product behind this is in
     whatever state the last run left it, and half-showing that is worse than
     showing none of it. */
  background: var(--bg);
  text-align: center;
  animation: ds-recap-in 260ms ease-out;
}
.ds-recap-title {
  max-width: 20ch;
  overflow-wrap: anywhere;
  font-size: var(--ds-recap-title);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  color: var(--text);
}
.ds-recap-subtitle {
  max-width: 44ch;
  overflow-wrap: anywhere;
  font-size: var(--ds-recap-subtitle);
  font-weight: var(--fw-normal);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}
.ds-recap-facts {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: center;
  gap: var(--spacing-8);
  max-width: 100%;
}
/* The accent is on the TOP EDGE and never on the type: a fact card is the one
   place a carrier's brand color reads as celebration rather than as a warning,
   and putting it behind the figure instead would make legibility depend on how
   light that carrier's accent happens to be. */
.ds-recap-fact {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-5);
  min-width: 0;
  padding: var(--spacing-9) var(--spacing-10);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: var(--radius-lg);
  background: var(--surface-elevated);
}
.ds-recap-fact-value {
  overflow-wrap: anywhere;
  font-size: var(--ds-recap-fact-value);
  font-weight: var(--fw-bold);
  line-height: var(--lh-none);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.ds-recap-fact-label {
  overflow-wrap: anywhere;
  font-size: var(--ds-recap-fact-label);
  font-weight: var(--fw-medium);
  line-height: var(--lh-normal);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}
.ds-recap-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--spacing-7);
}
.ds-recap-footer {
  font-size: var(--ds-recap-footer);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}
/* --text-on-accent, NEVER a literal white. --accent is a carrier token: Trident
   sets it to a lime with a luminance of ~0.85, where white text measures 1.2:1
   and simply is not there. The pairing token is what makes the same rule correct
   for a dark accent and a light one. */
.ds-recap-countdown {
  padding: var(--spacing-4) var(--spacing-8);
  border-radius: var(--radius-full);
  background: var(--accent);
  color: var(--text-on-accent);
  font-size: var(--ds-recap-footer);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  font-variant-numeric: tabular-nums;
}
@keyframes ds-recap-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ─── ui-demo-panel compact: the pill ─────────────────────────────────────────
   Floats and reserves nothing — that is the whole point. Positioned by drag, so
   the inline left/top set by the element beat these defaults. */
ui-demo-panel[compact] {
  position: fixed;
  top: auto;
  bottom: var(--spacing-7);
  /* BOTTOM-RIGHT, not bottom-left. The caption's lower third is bottom-left, so
     both defaulting to one corner put them on top of each other the moment nothing
     was spotlighted — and bottom-left is also where ui-app-sidebar keeps its footer
     clock, so the pill landed on it. Opposite corners by default; drag overrides. */
  right: var(--spacing-7);
  left: auto;
  width: auto;
  height: auto;
  border: 0;
  background: transparent;
  box-shadow: none;
  overflow: visible;
}
ui-demo-panel[compact].dp-dragging {
  /* No transition while dragging, or the pill lags the pointer. */
  transition: none;
  cursor: grabbing;
}
.dp-pill {
  display: flex;
  align-items: center;
  gap: var(--spacing-2);
  /* Roomier than it was: a 34px pill of ghost icons is a smudge, and this is the
     one control a presenter reaches for mid-sentence. */
  padding: var(--spacing-3) var(--spacing-5);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-full);
  background: var(--surface-elevated);
  box-shadow: var(--shadow-lg);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
/* Ghost buttons inherit --text-dim, which on a dark theme over an already dark app
   left the transport barely visible. Full text color: these are the only controls
   on screen during a run, so they should not be the faintest thing on it. */
.dp-pill ui-button,
.dp-pill ui-button .uib {
  /* Both the host AND the inner button: ui-button styles .uib directly, so a rule
     on the host alone does not reach the glyph. */
  color: var(--text);
}
.dp-grip {
  cursor: grab;
  padding: 0 var(--spacing-2);
  color: var(--text-muted);
  font-size: var(--fs-sm);
  letter-spacing: -2px;
  user-select: none;
  /* The grip is the ONLY drag handle: dragging from the buttons would make every
     press a potential nudge, and a transport you cannot trust is worse than none. */
  touch-action: none;
}
.dp-pill-progress {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
  padding: 0 var(--spacing-2);
}

/* ─── the pill, docked in the sidebar footer ──────────────────────────────────
   Its default home. That strip is ALREADY RESERVED, so unlike a floating pill it
   costs no content width, overlays nothing, and cannot collide with the caption —
   which removes the need for drag, snap, persistence and collision handling in the
   common case.

   Set by the host when it relocates the panel; `position: static` puts the pill
   back in normal flow so the footer lays it out. */
ui-demo-panel[compact].dp-footer {
  position: static;
  inset: auto;
  width: 100%;
}
ui-demo-panel[compact].dp-footer .dp-pill {
  /* Square off and fill: a floating lozenge inside a flush footer strip reads as
     something that has come loose. */
  width: 100%;
  border-radius: var(--radius-md);
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
ui-demo-panel[compact].dp-footer .dp-grip {
  /* Nothing to drag: it has a fixed home. */
  display: none;
}

/* ─── ui-data-region — a region that fetches its own data (ND-921) ────────────

   THE LOAD-BEARING RULE IS THE FIRST ONE. The authored content is shown in
   exactly one state — `ok`, when a response has been bound — and hidden while
   the region is idle, loading or errored. Every other arrangement was
   considered and each renders a wrong answer plausibly: content visible under a
   spinner shows the PREVIOUS selection's figures while the new ones are on the
   wire, and content visible under an error banner shows a subtree of em-dashes
   that reads as "we looked, there is nothing".

   Hidden by a class plus this rule rather than by writing style.display in JS —
   invariant 7. `> *` and not a descendant selector, so nothing inside the status
   strip is caught by it. */
ui-data-region.dr-idle > *:not(.dr-status),
ui-data-region.dr-loading > *:not(.dr-status),
ui-data-region.dr-errored > *:not(.dr-status) {
  display: none;
}
ui-data-region {
  display: block;
}
ui-data-region > .dr-status {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-3);
}
/* Only the error state gets the box. Idle and loading are quiet states and a
   framed gray panel around a spinner reads as a failure. */
ui-data-region.dr-errored > .dr-status {
  margin: var(--spacing-4) 0;
  padding: var(--spacing-5);
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: var(--danger-bg);
}
ui-data-region.dr-loading > .dr-status,
ui-data-region.dr-idle > .dr-status {
  padding: var(--spacing-4) 0;
}
.dr-pending {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-normal);
}
.dr-error-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--danger);
}
.dr-error-detail {
  font-size: var(--fs-sm);
  color: var(--text-muted);
  line-height: var(--lh-normal);
}
.dr-retry {
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.dr-retry:hover {
  background: var(--surface-3);
}

/* ─── ui-demo-panel: the pre-run prompt form ──────────────────────────────────
   Asking for a name in the room is the point of this surface, so it reads as an
   input step rather than as settings: same accent signature as the caption. */
.dp-prompt-form {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  padding: var(--spacing-6);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-md);
  background: var(--surface-elevated);
}
.dp-prompt-row {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  min-width: 0;
}
.dp-prompt-label {
  font-size: var(--fs-sm);
  color: var(--text-muted);
}
.dp-prompt-input {
  width: 100%;
  min-width: 0;
  padding: var(--spacing-4);
  font-size: var(--fs-md);
  color: var(--text);
  background: var(--surface-alt);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* ── Demo fixtures picker (elements/shared/demo-fixtures.js) ─────────────────

   The chord-invoked list of pre-baked demo values for the field the presenter is
   standing in. Not an element — a shared module mounts this into <body>, so the
   stacking level is the global band's documented slot for a body-mounted popover
   and NOT a level of its own. It is body-mounted rather than appended into the
   field (which is what .addr-suggest does) because a demo fills forms inside
   <ui-dialog>, and a list inside a scrolling dialog body is clipped by it.

   Positioned in JS: `top`/`left` are written from the field's own bounding box,
   so no offsets are declared here. Deliberately styled as a sibling of
   .addr-suggest rather than sharing its rules — the two look alike on purpose,
   because both are "pick one of these and it fills the field", and an author
   should not have to learn a second visual language for the demo one. */

.demo-fx {
  position: fixed;
  min-width: 280px;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: var(--elev-overlay);
  max-height: 320px;
  overflow-y: auto;
  z-index: var(--z-popover);
}

/* Names the field being filled, because the chord is global and the picker can
   open over a form with a dozen controls — "which box is this about" must not be
   a guess. Sticky so it survives a scroll through a long fixture list. */
.demo-fx-head {
  position: sticky;
  top: 0;
  padding: var(--spacing-3) var(--spacing-5);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  background: var(--surface-alt);
  border-bottom: 1px solid var(--border);
}

.demo-fx-item {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  padding: var(--spacing-3) var(--spacing-5);
  cursor: pointer;
  min-width: 0;
}

.demo-fx-item:hover,
.demo-fx-item-active {
  background: var(--accent-dim);
}

/* A fixture label is written for a room to read at a glance, so it wraps rather
   than being clipped — the .addr-suggest-item argument, same reason. */
.demo-fx-label {
  font-size: var(--fs-md);
  line-height: var(--lh-normal);
  color: var(--text);
  overflow-wrap: anywhere;
}

/* The second line carries the hint and how many fields the row fills. Quiet: it
   is orientation for the presenter, not the thing being chosen. */
.demo-fx-hint {
  font-size: var(--fs-sm);
  color: var(--text-dim);
  overflow-wrap: anywhere;
}

.demo-fx-empty {
  padding: var(--spacing-4) var(--spacing-5);
  font-size: var(--fs-base);
  color: var(--text-dim);
}

/* ══════════════════════════════════════════════════════════════════
   ui-live-premium — price unsaved input by running the filed version
   ══════════════════════════════════════════════════════════════════ */

ui-live-premium {
  display: block;
}

/* The documented normal mode renders nothing, so it must not be a box. As a
   block it is a grid item that opens an implicit row in ui-detail-view, and in
   any flex stack an empty child still consumes a `gap`. */
ui-live-premium[headless] {
  display: none;
}

ui-live-premium .lp-box {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
  padding: var(--spacing-4) var(--spacing-6);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

ui-live-premium .lp-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

ui-live-premium .lp-value {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* An unpriced figure is dimmed, never hidden — an absent number and a zero
   must not look the same. */
ui-live-premium .lp-value.lp-unpriced {
  color: var(--text-dim);
}

/* The filed code's own words about what it cannot price yet. */
ui-live-premium .lp-reason {
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

ui-live-premium .lp-note {
  font-size: var(--fs-xs);
  color: var(--text-dim);
}

/* Dim the stale figure WHILE recalculating rather than blanking it. A number
   that disappears on every keystroke is unreadable; one that fades is legible
   and still honest about being out of date. */
ui-live-premium .lp-box.lp-busy .lp-value {
  opacity: 0.5;
}

/* ══════════════════════════════════════════════════════════════════
   ui-worksheet — ordered calculation steps as a phased waterfall
   ══════════════════════════════════════════════════════════════════ */

ui-worksheet {
  display: block;
}

ui-worksheet .wks {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
}

ui-worksheet .wks-empty {
  padding: var(--spacing-8);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-md);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
}

ui-worksheet .wks-scopes,
ui-worksheet .wks-lines {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-4);
}

ui-worksheet .wks-scope {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-6);
  font-family: inherit;
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  color: var(--text-muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  cursor: pointer;
}

ui-worksheet .wks-scope:hover {
  color: var(--text);
  border-color: var(--accent);
}

ui-worksheet .wks-scope[aria-pressed='true'] {
  color: var(--text-on-accent);
  background: var(--accent);
  border-color: var(--accent);
}

ui-worksheet .wks-count {
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

ui-worksheet .wks-line {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--spacing-1);
  min-width: 96px;
  padding: var(--spacing-4) var(--spacing-6);
  font-family: inherit;
  text-align: left;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

ui-worksheet .wks-line:hover {
  border-color: var(--accent);
}

ui-worksheet .wks-line[aria-pressed='true'] {
  border-color: var(--accent);
  background: var(--accent-dim);
}

ui-worksheet .wks-line-unset {
  opacity: 0.6;
}

ui-worksheet .wks-line-code {
  font-family: var(--mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.04em;
}

ui-worksheet .wks-line-total {
  font-size: var(--fs-md);
  font-variant-numeric: tabular-nums;
}

ui-worksheet .wks-line-count {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

ui-worksheet .wks-filter {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-4);
  font-size: var(--fs-base);
  color: var(--text-muted);
  cursor: pointer;
}

ui-worksheet .wks-phases {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
}

ui-worksheet .wks-phase {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

ui-worksheet .wks-phase-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-6);
  width: 100%;
  padding: var(--spacing-5) var(--spacing-6);
  font-family: inherit;
  text-align: left;
  color: var(--text);
  background: var(--surface-2);
  border: 0;
  cursor: pointer;
}

ui-worksheet .wks-phase-head:hover {
  background: var(--surface-3);
}

ui-worksheet .wks-phase-titles {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-1);
  flex: 1;
  min-width: 0;
}

ui-worksheet .wks-phase-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
}

ui-worksheet .wks-phase-caption {
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

ui-worksheet .wks-phase-result {
  font-family: var(--mono);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

ui-worksheet .wks-phase[data-collapsed] .wks-phase-body {
  display: none;
}

ui-worksheet .wks-table {
  width: 100%;
  border-collapse: collapse;
}

ui-worksheet .wks-step > td {
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-base);
  border-top: 1px solid var(--border);
  vertical-align: top;
}

ui-worksheet .wks-step:hover > td {
  background: var(--surface-2);
}

ui-worksheet .wks-step-noop > td {
  color: var(--text-dim);
}

ui-worksheet .wks-seq {
  width: 40px;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

ui-worksheet .wks-op {
  width: 88px;
}

ui-worksheet .wks-token {
  display: inline-block;
  padding: var(--spacing-1) var(--spacing-3);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  border-radius: var(--radius-xs);
  color: var(--text);
  background: var(--surface-3);
}

ui-worksheet .wks-token-base {
  color: var(--text);
  background: var(--surface-3);
}

ui-worksheet .wks-token-add,
ui-worksheet .wks-token-surcharge {
  color: var(--warning);
  background: var(--warning-bg);
}

ui-worksheet .wks-token-multiply {
  color: var(--info);
  background: var(--info-bg);
}

ui-worksheet .wks-token-discount {
  color: var(--success);
  background: var(--success-bg);
}

ui-worksheet .wks-token-floor,
ui-worksheet .wks-token-cap {
  color: var(--danger);
  background: var(--danger-bg);
}

ui-worksheet .wks-token-subtotal {
  color: var(--text-muted);
  background: transparent;
}

ui-worksheet .wks-step-noop .wks-token {
  color: var(--text-dim);
  background: var(--surface-2);
}

ui-worksheet .wks-desc {
  color: inherit;
}

ui-worksheet .wks-src {
  display: table-cell;
  white-space: nowrap;
}

ui-worksheet .wks-kind {
  display: inline-block;
  margin-right: var(--spacing-3);
  padding: var(--spacing-1) var(--spacing-3);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  background: var(--surface-3);
  border-radius: var(--radius-2xs);
}

ui-worksheet .wks-name {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
}

ui-worksheet .wks-after {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

ui-worksheet .wks-hidden-note > td {
  padding: var(--spacing-4) var(--spacing-6);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  background: var(--surface-2);
  border-top: 1px solid var(--border);
}

/* ══════════════════════════════════════════════════════════════════
   ui-problem-detail — RFC 9457 problem documents, and bodies that
   are not one
   ══════════════════════════════════════════════════════════════════ */

ui-problem-detail {
  display: block;
}

ui-problem-detail .pd-empty {
  padding: var(--spacing-8);
  text-align: center;
  color: var(--text-muted);
  font-size: var(--fs-md);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
}

ui-problem-detail .pd {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-5);
  padding: var(--spacing-7);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--surface);
}

ui-problem-detail .pd-danger {
  border-left-color: var(--danger);
  background: var(--danger-bg);
}

ui-problem-detail .pd-warning {
  border-left-color: var(--warning);
  background: var(--warning-bg);
}

ui-problem-detail .pd-info {
  border-left-color: var(--info);
  background: var(--info-bg);
}

ui-problem-detail .pd-head {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-5);
}

ui-problem-detail .pd-status {
  font-family: var(--mono);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

ui-problem-detail .pd-title {
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-problem-detail .pd-detail {
  margin: 0;
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

ui-problem-detail .pd-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--spacing-2) var(--spacing-6);
  margin: 0;
}

ui-problem-detail .pd-meta-key {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}

ui-problem-detail .pd-meta-value {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

ui-problem-detail .pd-errors-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}

ui-problem-detail .pd-errors {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

ui-problem-detail .pd-error {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
  padding: var(--spacing-5) var(--spacing-6);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
}

ui-problem-detail .pd-error:hover {
  border-color: var(--accent);
}

ui-problem-detail .pd-error-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--spacing-4);
}

ui-problem-detail .pd-pointer {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text);
  overflow-wrap: anywhere;
}

ui-problem-detail .pd-code {
  padding: var(--spacing-1) var(--spacing-3);
  font-family: var(--mono);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  background: var(--surface-3);
  border-radius: var(--radius-2xs);
}

ui-problem-detail .pd-error-message {
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  color: var(--text);
}

ui-problem-detail .pd-allowed-label {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-dim);
}

ui-problem-detail .pd-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-3);
}

ui-problem-detail .pd-chip {
  padding: var(--spacing-1) var(--spacing-4);
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
}

ui-problem-detail .pd-allowed-table {
  border-collapse: collapse;
  font-family: var(--mono);
  font-size: var(--fs-xs);
}

ui-problem-detail .pd-allowed-table th,
ui-problem-detail .pd-allowed-table td {
  padding: var(--spacing-2) var(--spacing-5);
  text-align: right;
  border: 1px solid var(--border);
  color: var(--text);
}

ui-problem-detail .pd-allowed-table th {
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  background: var(--surface-3);
}

/* ══════════════════════════════════════════════════════════════════
   ui-json-editor — editable JSON with a gutter and pointer marking
   ══════════════════════════════════════════════════════════════════ */

ui-json-editor {
  display: block;
}

ui-json-editor .je {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
}

ui-json-editor .je-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-5);
  padding: var(--spacing-3) var(--spacing-6);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}

ui-json-editor .je-label {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
}

ui-json-editor .je-format {
  padding: var(--spacing-2) var(--spacing-5);
  font-family: inherit;
  font-size: var(--fs-xs);
  color: var(--text);
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  cursor: pointer;
}

ui-json-editor .je-format:hover {
  border-color: var(--accent);
}

/* The gutter is taken OUT OF FLOW. As a flex item its intrinsic height is the
   full line count, which made the row 488px tall against a 272px textarea and left
   overflow:hidden with nothing to scroll, so the numbers never tracked the text. */
ui-json-editor .je-body {
  position: relative;
  min-height: 0;
}

ui-json-editor .je-gutter,
ui-json-editor .je-input {
  font-family: var(--mono);
  font-size: var(--fs-base);
  line-height: var(--lh-relaxed);
}

ui-json-editor .je-gutter {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: var(--je-gutter-width);
  padding: var(--spacing-5) var(--spacing-4);
  box-sizing: border-box;
  text-align: right;
  color: var(--text-dim);
  background: var(--surface-2);
  border-right: 1px solid var(--border);
  overflow: hidden;
  user-select: none;
}

ui-json-editor .je-gutter-line {
  font-variant-numeric: tabular-nums;
  white-space: pre;
}

ui-json-editor .je-gutter-error {
  color: var(--danger);
  font-weight: var(--fw-bold);
}

ui-json-editor .je-field {
  position: relative;
  margin-left: var(--je-gutter-width);
  min-width: 0;
}

/* Two layers: .je-marks clips to the visible box so a band for a line below the
   fold does not paint outside the editor, and .je-marks-scroll is what moves. */
ui-json-editor .je-marks {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: var(--z-raised);
  pointer-events: none;
}

ui-json-editor .je-marks-scroll {
  position: absolute;
  top: var(--spacing-5);
  left: 0;
  right: 0;
}

ui-json-editor .je-mark {
  position: absolute;
  left: 0;
  right: 0;
  background: var(--accent-dim);
  border-left: 2px solid var(--accent);
}

ui-json-editor .je-input {
  position: relative;
  z-index: var(--z-elevated);
  display: block;
  width: 100%;
  padding: var(--spacing-5) var(--spacing-6);
  color: var(--text);
  background: transparent;
  border: 0;
  outline: none;
  resize: vertical;
  white-space: pre;
  overflow: auto;
  box-sizing: border-box;
}

ui-json-editor .je-status {
  padding: var(--spacing-3) var(--spacing-6);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  background: var(--surface-2);
  border-top: 1px solid var(--border);
  min-height: 18px;
}

ui-json-editor .je-status-ok {
  color: var(--success);
}

ui-json-editor .je-status-error {
  color: var(--danger);
  font-family: var(--mono);
}

/* ══════════════════════════════════════════════════════════════════
   ui-form-field type="select" searchable — a filter over a NATIVE
   select. The select is not replaced: it keeps its own keyboard, its
   mobile picker and its form semantics.
   ══════════════════════════════════════════════════════════════════ */

.ff-select-search-group {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-3);
}

.ff-select-search {
  width: 100%;
  padding: var(--spacing-4) var(--spacing-5);
  font-family: inherit;
  font-size: var(--fs-xs);
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  outline: none;
  box-sizing: border-box;
}

.ff-select-search:focus {
  border-color: var(--accent);
}

.ff-select-search-note {
  font-size: var(--fs-2xs);
  color: var(--text-muted);
  min-height: 12px;
}

/* ══════════════════════════════════════════════════════════════════
   elements/analysis — com-oneshield-nukor-analysis's own elements.
   Domain-coupled by decision; kept out of the generic families above.
   ══════════════════════════════════════════════════════════════════ */

/* ui-analysis-pivot-table — cross-tab, scoped to the plugin-local tag. */
ui-analysis-pivot-table { display: block; min-width: 0; }
/* Same thin scrollbar the rest of the app uses; Chrome's default is drawn
   from the document's color-scheme and is far heavier. */
ui-analysis-pivot-table .pvt-wrap,
ui-analysis-pivot-designer .rpt-field-list,
ui-analysis-pivot-designer .rpt-result,
.rpt-value-list {
  scrollbar-width: thin;
  scrollbar-color: var(--text-dim) transparent;
}
/* position: relative makes this cells' shared offsetParent — ND-235's
   drop-target geometry reads offsetLeft/offsetTop against it, never a
   mid-animation rect (contract §2.7). */
ui-analysis-pivot-table .pvt-wrap { position: relative; overflow: auto; max-height: 560px; border: 1px solid var(--border); border-radius: var(--radius-sm); }
/* Every hoverable table section is a drag source (contract §2.5.1, §2.5.5). */
ui-analysis-pivot-table [data-rpt-drag] { cursor: grab; }
ui-analysis-pivot-table [data-rpt-drag]:active { cursor: grabbing; }
ui-analysis-pivot-table .pvt-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
ui-analysis-pivot-table .pvt-table th,
ui-analysis-pivot-table .pvt-table td {
  padding: var(--spacing-3) var(--spacing-5);
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  text-align: left;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
}
ui-analysis-pivot-table .pvt-rowhdr { max-width: 280px; }
ui-analysis-pivot-table .pvt-table th:last-child,
ui-analysis-pivot-table .pvt-table td:last-child { border-right: none; }
ui-analysis-pivot-table .pvt-table thead th { position: sticky; top: 0; color: var(--text-muted); background: var(--surface-3); font-weight: var(--fw-semibold); }
/* thead th's own background above out-specifies the hover and source
   classes (one class vs. two tag selectors) — restated here so the wash
   shows on header cells too, which is the whole of a column field's section. */
ui-analysis-pivot-table .pvt-table thead th.rpt-table-hover,
ui-analysis-pivot-table .pvt-table thead th.rpt-zone-hover-link {
  background: color-mix(in srgb, var(--accent) 12%, var(--surface-3));
}
ui-analysis-pivot-table .pvt-table thead th.rpt-table-source { background: color-mix(in srgb, var(--accent) 32%, transparent); }
ui-analysis-pivot-table .pvt-table .pvt-group { text-align: center; color: var(--text); white-space: normal; }
ui-analysis-pivot-table .pvt-table .pvt-col { text-align: right; }
ui-analysis-pivot-table .pvt-corner { text-align: left; color: var(--text); max-width: none; }
ui-analysis-pivot-table .pvt-table thead th.pvt-colfield { text-align: right; color: var(--text-muted); font-weight: var(--fw-medium); max-width: none; }
/* The label column reads as the left edge of the value area: no rule between it and the values,
   and none under the blank corner above the row-field names. */
ui-analysis-pivot-table[field-headers] .pvt-table thead th.pvt-colfield { text-align: left; }
ui-analysis-pivot-table[field-headers] .pvt-table :is(th.pvt-colfield, .pvt-label-pad) { border-right-style: hidden; }
ui-analysis-pivot-table[field-headers] .pvt-table thead th.pvt-pad:not(.pvt-label-pad) { border-bottom-style: hidden; }
/* Field names (Loss Cause, Loss City) read as labels; the values beside them as data. */
ui-analysis-pivot-table[field-headers] .pvt-table thead :is(th.pvt-colfield, th.pvt-corner) { font-size: var(--fs-xs); font-weight: var(--fw-medium); color: var(--text-muted); }
ui-analysis-pivot-table[field-headers] .pvt-table thead :is(th.pvt-group, th.pvt-col) { color: var(--text); }
ui-analysis-pivot-table .pvt-rowhdr { color: var(--text); font-weight: var(--fw-semibold); background: var(--surface-2); }
ui-analysis-pivot-table .pvt-rowhdr-inner { color: var(--text-muted); font-weight: var(--fw-normal); }
ui-analysis-pivot-table .pvt-repeat { border-bottom-color: transparent; }
/* A blank GROUP value — the field's own value is null — reads "(blank)" in
   dim italic. Never `—`, which means "no data for this cell" instead. */
ui-analysis-pivot-table .pvt-null { color: var(--text-dim); font-style: italic; font-weight: var(--fw-normal); }
ui-analysis-pivot-table .pvt-table .pvt-num { text-align: right; color: var(--text); font-family: var(--mono); font-variant-numeric: tabular-nums; }
ui-analysis-pivot-table .pvt-total, ui-analysis-pivot-table .pvt-total-head { background: var(--surface-2); font-weight: var(--fw-semibold); }
ui-analysis-pivot-table .pvt-total-row td, ui-analysis-pivot-table .pvt-total-row th { background: var(--surface-2); font-weight: var(--fw-semibold); border-top: 1px solid var(--surface-3); }
ui-analysis-pivot-table .pvt-grand { color: var(--text); }
ui-analysis-pivot-table .pvt-subtotal-label { color: var(--text-muted); }
ui-analysis-pivot-table .pvt-table .pvt-overflow-cell { overflow: visible; }
ui-analysis-pivot-table .pvt-overflow { display: block; width: 0; overflow: visible; white-space: nowrap; }
ui-analysis-pivot-table .pvt-total-row :is(.pvt-overflow-cell, .pvt-overflow-fill):not(.pvt-axis-end) { border-right-color: var(--surface-2); }
/* A total or subtotal cell while its shape's fetch is outstanding — never
   the previous shape's number (contract §2.4, §3.2). */
ui-analysis-pivot-table [aria-busy="true"] { color: var(--text-dim); font-style: italic; font-weight: var(--fw-normal); }
ui-analysis-pivot-table .pvt-empty { padding: var(--spacing-8); text-align: center; color: var(--text-dim); font-size: var(--fs-md); border: 1px dashed var(--border); border-radius: var(--radius-sm); }
ui-analysis-pivot-table .pvt-notice { margin-top: var(--spacing-4); font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--text-muted); }
ui-analysis-pivot-table .pvt-notice-warn { padding: var(--spacing-4) var(--spacing-5); color: var(--warning); background: var(--warning-bg); border-radius: var(--radius-xs); }
ui-analysis-pivot-table .pvt-notice-error { margin-top: 0; margin-bottom: var(--spacing-4); padding: var(--spacing-4) var(--spacing-5); color: var(--danger); background: var(--danger-bg); border-radius: var(--radius-xs); }

/* ND-235 invariant class names (contract §2.5.4, §2.5.5) — defined here
   once, used by ui-analysis-pivot-designer.js in both directions. Fades in over
   120ms, matching the sketch, rather than snapping on. */
ui-analysis-pivot-table .rpt-zone-hover-link, ui-analysis-pivot-designer .rpt-zone-hover-link { background: var(--accent-dim); transition: background 120ms, box-shadow 120ms; }
ui-analysis-pivot-table .rpt-table-hover, ui-analysis-pivot-designer .rpt-table-hover { background: var(--accent-dim); transition: background 120ms, box-shadow 120ms; }
/* A table cell takes its accent border per side, so the range reads as one
   outlined block instead of a grid of outlined cells; a chart node is its
   own block and keeps a whole outline. ui-analysis-pivot-designer.js sets the
   sides. */
ui-analysis-pivot-chart .rpt-table-hover, ui-analysis-pivot-chart .rpt-zone-hover-link { outline: 1px solid var(--accent); }
ui-analysis-pivot-table .rpt-edged, ui-analysis-pivot-table .rpt-source-edged {
  box-shadow:
    inset 0 var(--rpt-edge-top, 0px) 0 0 var(--accent),
    inset calc(-1 * var(--rpt-edge-right, 0px)) 0 0 0 var(--accent),
    inset 0 calc(-1 * var(--rpt-edge-bottom, 0px)) 0 0 var(--accent),
    inset var(--rpt-edge-left, 0px) 0 0 0 var(--accent);
}
ui-analysis-pivot-table .rpt-edge-top { --rpt-edge-top: 1px; }
ui-analysis-pivot-table .rpt-edge-right { --rpt-edge-right: 1px; }
ui-analysis-pivot-table .rpt-edge-bottom { --rpt-edge-bottom: 1px; }
ui-analysis-pivot-table .rpt-edge-left { --rpt-edge-left: 1px; }
ui-analysis-pivot-table .rpt-source-edge-top { --rpt-edge-top: 2px; }
ui-analysis-pivot-table .rpt-source-edge-right { --rpt-edge-right: 2px; }
ui-analysis-pivot-table .rpt-source-edge-bottom { --rpt-edge-bottom: 2px; }
ui-analysis-pivot-table .rpt-source-edge-left { --rpt-edge-left: 2px; }
ui-analysis-pivot-designer .rpt-chip:is(.rpt-table-hover-chip, .rpt-zone-hover-chip) { outline: 1px solid var(--accent); outline-offset: -1px; background: color-mix(in srgb, var(--accent) 12%, var(--surface-3)); }
/* Same specificity problem as thead th, from .pvt-total-row's two-selector
   background: a number's section includes its total cells. */
ui-analysis-pivot-table .pvt-total-row td.rpt-table-hover,
ui-analysis-pivot-table .pvt-total-row th.rpt-table-hover,
ui-analysis-pivot-table .pvt-total-row td.rpt-zone-hover-link,
ui-analysis-pivot-table .pvt-total-row th.rpt-zone-hover-link {
  background: color-mix(in srgb, var(--accent) 12%, var(--surface-2));
}
/* Press/source variant of the hover: a stronger wash and a 2px perimeter.
   Table cells get the perimeter per side like the hover; chart nodes outline. */
ui-analysis-pivot-table .rpt-table-source, ui-analysis-pivot-designer .rpt-table-source {
  background: color-mix(in srgb, var(--accent) 32%, transparent);
}
ui-analysis-pivot-chart .rpt-table-source { outline: 2px solid var(--accent); }
ui-analysis-pivot-designer :is(.rpt-chip, .rpt-field).rpt-table-source-chip {
  outline: 1px solid var(--accent);
  outline-offset: -1px;
  background: color-mix(in srgb, var(--accent) 20%, var(--surface-3));
}
/* The dragged field, lime, wherever it appears during a live preview
   (contract §2.5.3) — !important because it must win over a cell's own
   .pvt-total/.pvt-rowhdr background and a chip's own .rpt-chip background. */
ui-analysis-pivot-table .rpt-new, ui-analysis-pivot-designer .rpt-new {
  background: color-mix(in srgb, var(--accent) 28%, transparent) !important;
}

/* Table/chart drop-target overlay (contract §2.5.2, §2.5.3). The chart's
   stays inside ui-analysis-pivot-chart; the table's is a fixed document.body
   portal (ui-analysis-pivot-designer.js) so a gutter slot escapes .pvt-wrap's own
   overflow:auto — unqualified selectors so both mount points style alike. */
.rpt-slots { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.rpt-slot { position: absolute; pointer-events: none; border-radius: var(--radius-xs); }
.rpt-slot-cover {
  border: 1px dashed color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.rpt-slot-target.rpt-slot-cover {
  border: 1px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
}
.rpt-slot-bar-v::after {
  content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 3px; transform: translateX(-50%);
  background: var(--accent); opacity: 0.35; border-radius: var(--radius-2xs);
}
.rpt-slot-bar-h::after {
  content: ''; position: absolute; top: 50%; left: 0; right: 0; height: 3px; transform: translateY(-50%);
  background: var(--accent); opacity: 0.35; border-radius: var(--radius-2xs);
}
.rpt-slot-target.rpt-slot-bar-v::after, .rpt-slot-target.rpt-slot-bar-h::after { opacity: 1; }
.rpt-slot-bad.rpt-slot-cover { border-color: var(--danger); background: var(--danger-bg); }
.rpt-slot-bad.rpt-slot-bar-v::after, .rpt-slot-bad.rpt-slot-bar-h::after { background: var(--danger); opacity: 0.5; }

/* ui-analysis-pivot-designer — field rail + live cross-tab whose headers are the chips (contract §2.2). */
ui-analysis-pivot-designer { display: block; container-type: inline-size; }
ui-detail-content:has(> ui-content-panel.cp-active ui-analysis-pivot-designer) { padding: var(--spacing-5); }
ui-analysis-pivot-designer .rpt-workbench { display: grid; grid-template-columns: 230px minmax(0, 1fr); gap: var(--spacing-7); align-items: start; }
@container (max-width: 860px) { ui-analysis-pivot-designer .rpt-workbench { grid-template-columns: minmax(0, 1fr); } }
/* In a content panel the workbench fills it: the rail spans the panel's height and the main column scrolls on its own. */
ui-content-panel > ui-analysis-pivot-designer { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
ui-content-panel > ui-analysis-pivot-designer > .rpt-workbench { flex: 1 1 auto; min-height: 0; align-items: stretch; }
ui-content-panel > ui-analysis-pivot-designer > .rpt-workbench > .rpt-rail { min-height: 0; }
ui-content-panel > ui-analysis-pivot-designer > .rpt-workbench > .rpt-rail .rpt-field-list { flex: 1 1 auto; min-height: 120px; max-height: none; }
ui-content-panel > ui-analysis-pivot-designer > .rpt-workbench > .rpt-main { min-height: 0; overflow: auto; scrollbar-width: thin; scrollbar-color: var(--text-dim) transparent; }
@container (max-width: 860px) {
  ui-content-panel > ui-analysis-pivot-designer > .rpt-workbench { flex: none; align-items: start; }
  ui-content-panel > ui-analysis-pivot-designer > .rpt-workbench > .rpt-rail .rpt-field-list { max-height: 320px; }
  ui-content-panel > ui-analysis-pivot-designer > .rpt-workbench > .rpt-main { overflow: visible; }
}

ui-analysis-pivot-designer .rpt-rail { display: flex; flex-direction: column; gap: var(--spacing-3); padding: var(--spacing-6); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
ui-analysis-pivot-designer .rpt-main { display: flex; flex-direction: column; gap: var(--spacing-5); min-width: 0; }
ui-analysis-pivot-designer .rpt-rail-label { margin-top: var(--spacing-4); font-size: var(--fs-xs); font-weight: var(--fw-semibold); letter-spacing: 0.6px; text-transform: uppercase; color: var(--text-dim); }
ui-analysis-pivot-designer .rpt-rail-foot { margin-top: var(--spacing-5); }
ui-analysis-pivot-designer .rpt-rail[hidden] { display: none; }
ui-analysis-pivot-designer .rpt-rail-value { font-size: var(--fs-base); font-weight: var(--fw-semibold); color: var(--text); }
ui-analysis-pivot-designer .rpt-workbench.rpt-workbench-unrailed { grid-template-columns: minmax(0, 1fr); }
ui-analysis-pivot-designer .rpt-choose { display: flex; flex-direction: column; gap: var(--spacing-4); width: 100%; max-width: 480px; margin: var(--spacing-9) auto; padding: var(--spacing-7); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm); }
ui-analysis-pivot-designer .rpt-choose-title { font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--text); }
ui-analysis-pivot-designer .rpt-select, ui-analysis-pivot-designer .rpt-input { width: 100%; padding: var(--spacing-3) var(--spacing-4); color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-xs); font-family: inherit; font-size: var(--fs-base); }
ui-analysis-pivot-designer .rpt-hint { font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--text-dim); }

ui-analysis-pivot-designer .rpt-field-list { display: flex; flex-direction: column; max-height: 320px; overflow: auto; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-xs); }
ui-analysis-pivot-designer .rpt-field-group { position: sticky; top: 0; padding: var(--spacing-2) var(--spacing-4); font-size: var(--fs-2xs); font-weight: var(--fw-semibold); letter-spacing: 0.5px; text-transform: uppercase; color: var(--text-dim); background: var(--surface-2); }
ui-analysis-pivot-designer .rpt-field { display: flex; align-items: center; gap: var(--spacing-3); width: 100%; padding: var(--spacing-3) var(--spacing-4); text-align: left; color: var(--text); background: none; border: none; border-bottom: 1px solid var(--border); font-family: inherit; font-size: var(--fs-base); cursor: grab; user-select: none; touch-action: none; }
ui-analysis-pivot-designer .rpt-field:hover { background: var(--surface-2); }
ui-analysis-pivot-designer .rpt-field:active { cursor: grabbing; }
ui-analysis-pivot-designer .rpt-field-used .rpt-field-label { color: var(--text-muted); }
ui-analysis-pivot-designer .rpt-field-where { flex: 0 0 auto; padding: 0 var(--spacing-2); color: var(--text-muted); border: 1px solid var(--border); border-radius: var(--radius-2xs); font-size: var(--fs-xs); }
ui-analysis-pivot-designer .rpt-field:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
ui-analysis-pivot-designer .rpt-field .rpt-chip-funnel { visibility: hidden; }
ui-analysis-pivot-designer .rpt-field:hover .rpt-chip-funnel,
ui-analysis-pivot-designer .rpt-field:focus-within .rpt-chip-funnel,
ui-analysis-pivot-designer .rpt-field-filtered .rpt-chip-funnel { visibility: visible; }
ui-analysis-pivot-designer .rpt-field .rpt-chip-filterval { align-self: flex-start; max-width: 100%; margin-top: var(--spacing-1); }
ui-analysis-pivot-designer .rpt-field-used .rpt-field-role { color: var(--accent); }
ui-analysis-pivot-designer .rpt-field-role { flex: 0 0 auto; width: 14px; text-align: center; color: var(--text-dim); font-family: var(--mono); font-size: var(--fs-xs); }
ui-analysis-pivot-designer .rpt-field-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
ui-analysis-pivot-designer .rpt-field-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ui-analysis-pivot-designer .rpt-field-desc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-xs); color: var(--text-dim); }
ui-analysis-pivot-designer .rpt-field-agg { flex: 0 0 auto; color: var(--accent); font-family: var(--mono); font-size: var(--fs-2xs); }

ui-analysis-pivot-designer .rpt-rail.rpt-rail-remove-target { outline: 2px dashed var(--danger); background: var(--danger-bg); }
ui-analysis-pivot-designer .rpt-rail.rpt-remove-hot { outline: 2px dashed var(--danger); background: var(--danger-bg); }
ui-analysis-pivot-designer.is-dragging [data-rpt-drag] { cursor: grabbing; }
/* Belt-and-suspenders for rpt-drag.js's preventDefault on press — a drag
   started with a pointer type it does not cover should still select nothing. */
ui-analysis-pivot-designer.is-dragging { user-select: none; }

/* The table's header scaffold (contract §2.3): an empty axis is a dashed
   placeholder cell, and every field-name header cell hosts that field's chip. */
ui-analysis-pivot-table .pvt-table thead th.pvt-hosted { padding: 0 var(--spacing-2) var(--spacing-2); max-width: none; overflow: visible; }
ui-analysis-pivot-table .pvt-table thead th:is(.pvt-corner.pvt-hosted, .pvt-colfield.pvt-hosted, .pvt-col.pvt-hosted, .pvt-group.pvt-placeholder, .pvt-pad):not(.rpt-table-hover, .rpt-zone-hover-link, .rpt-table-source) { background: transparent; }
ui-analysis-pivot-table .pvt-table thead th.pvt-pad.pvt-hosted { text-align: right; vertical-align: bottom; }
ui-analysis-pivot-table .pvt-table thead th.pvt-corner.pvt-hosted { padding-inline: 0 var(--spacing-2); }
/* A column value is a tile the height of a chip, in a cell padded below like the chip cells, so
   stacked header rows keep the same gap the chips do. The cell is bare; the tile carries the look. */
ui-analysis-pivot-table[scaffold] .pvt-table thead th.pvt-tiled { padding: 0 0 var(--spacing-2); border-style: none; background: transparent; box-shadow: none; }
ui-analysis-pivot-table[scaffold] .pvt-table thead th.pvt-tiled:is(:last-child, :has(+ th.pvt-total-head)) { padding-right: var(--spacing-2); }
ui-analysis-pivot-table[scaffold] .pvt-tile { padding: var(--spacing-2) var(--spacing-5); background: var(--surface-3); border: 1px solid var(--border); border-left: none; }
ui-analysis-pivot-table[scaffold] .pvt-tiled:is(.rpt-table-hover, .rpt-zone-hover-link) > .pvt-tile { background: color-mix(in srgb, var(--accent) 12%, var(--surface-3)); }
ui-analysis-pivot-table[scaffold] .pvt-tiled.rpt-table-source > .pvt-tile { background: color-mix(in srgb, var(--accent) 32%, transparent); }
ui-analysis-pivot-table[scaffold] .pvt-tiled:is(.rpt-edged, .rpt-source-edged) > .pvt-tile {
  box-shadow:
    inset 0 var(--rpt-edge-top, 0px) 0 0 var(--accent),
    inset calc(-1 * var(--rpt-edge-right, 0px)) 0 0 0 var(--accent),
    inset 0 calc(-1 * var(--rpt-edge-bottom, 0px)) 0 0 var(--accent),
    inset var(--rpt-edge-left, 0px) 0 0 0 var(--accent);
}
ui-analysis-pivot-table .pvt-table thead :is(th.pvt-colfield.pvt-hosted, :is(th.pvt-corner, th.pvt-label-pad) + th.pvt-col.pvt-hosted) { padding-left: 0; }
/* Only the data carries the grid: the chip and pad cells are borderless, and the table has no frame.
   The first column tile draws the value area's left edge; the value chips keep their bottom edge. */
ui-analysis-pivot-table[scaffold] .pvt-wrap { border: none; }
ui-analysis-pivot-table[scaffold] .pvt-table thead th:is(.pvt-hosted, .pvt-pad) { border-style: none; }
ui-analysis-pivot-table[scaffold] .pvt-table thead th.pvt-colfield + th.pvt-tiled > .pvt-tile { border-left: 1px solid var(--border); }
ui-analysis-pivot-table[scaffold] .pvt-table thead tr:last-child :is(th.pvt-col.pvt-hosted, th.pvt-label-pad) { border-bottom-style: solid; }
ui-analysis-pivot-designer .rpt-placeholder { display: flex; flex-direction: column; gap: var(--spacing-3); min-height: 56px; padding: var(--spacing-4); text-align: left; background: var(--bg); border: 1px dashed var(--border); border-radius: var(--radius-xs); cursor: pointer; }
ui-analysis-pivot-designer .rpt-placeholder-label { font-size: var(--fs-sm); font-weight: var(--fw-normal); color: var(--text-muted); }
ui-analysis-pivot-designer .rpt-placeholder-hint { padding: 0 var(--spacing-3); font-size: var(--fs-sm); font-weight: var(--fw-normal); font-style: italic; color: var(--text-dim); }
/* Every placeholder that accepts the dragged field, for the life of the drag (contract §2.5.2). */
ui-analysis-pivot-designer .rpt-placeholder-candidate { border-style: solid; border-color: var(--accent); }
/* The same box as a header chip: padding, border, font size and line height, with a one-line icon. */
ui-analysis-pivot-designer .rpt-iconbtn.rpt-swap { display: inline-flex; align-items: center; padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); line-height: var(--lh-relaxed); vertical-align: bottom; }
ui-analysis-pivot-designer .rpt-iconbtn.rpt-swap ui-icon { display: inline-flex; width: 1lh; height: 1lh; }
ui-analysis-pivot-designer .rpt-iconbtn.rpt-swap :is(.ico, svg) { width: 100%; height: 100%; }
ui-analysis-pivot-designer .rpt-result-frame { display: flex; flex-direction: column; gap: var(--spacing-5); }

ui-analysis-pivot-designer .rpt-chip { display: flex; align-items: center; gap: var(--spacing-3); padding: var(--spacing-3) var(--spacing-4); color: var(--text); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-xs); font-size: var(--fs-base); cursor: grab; user-select: none; touch-action: none; }
ui-analysis-pivot-designer .rpt-chip::before { content: '⋮⋮'; letter-spacing: -2px; color: var(--text-dim); font-size: var(--fs-xs); }
ui-analysis-pivot-designer .rpt-chip:active { cursor: grabbing; }
ui-analysis-pivot-designer .rpt-chip-filter { cursor: pointer; }
ui-analysis-pivot-designer .rpt-chip-incomplete { border-color: var(--danger); }
ui-analysis-pivot-designer .rpt-chip-head { gap: var(--spacing-2); padding: var(--spacing-2) var(--spacing-3); text-align: left; font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text); }
ui-analysis-pivot-designer .rpt-chip-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ui-analysis-pivot-designer .rpt-chip-tag { flex: 0 0 auto; color: var(--info); font-family: var(--mono); font-size: var(--fs-2xs); }
ui-analysis-pivot-designer .rpt-grain-tag { flex: 0 0 auto; color: var(--accent); font-family: var(--mono); font-size: var(--fs-2xs); cursor: pointer; padding: 0 var(--spacing-2); border-radius: var(--radius-2xs); }
ui-analysis-pivot-designer .rpt-grain-tag:hover { background: var(--accent-dim); }
ui-analysis-pivot-designer .rpt-chip-x { flex: 0 0 auto; margin-left: auto; padding: 0 var(--spacing-1); color: var(--text-dim); background: none; border: none; font-size: var(--fs-lg); line-height: var(--lh-none); cursor: pointer; }
ui-analysis-pivot-designer .rpt-chip-x:hover { color: var(--danger); }
ui-analysis-pivot-designer .rpt-chip-funnel { flex: 0 0 auto; display: inline-flex; align-items: center; padding: var(--spacing-1); color: var(--text-dim); background: none; border: none; border-radius: var(--radius-2xs); cursor: pointer; }
ui-analysis-pivot-designer .rpt-chip-funnel:hover { color: var(--text); background: var(--surface-2); }
ui-analysis-pivot-designer .rpt-chip-funnel-on { color: var(--accent); }
ui-analysis-pivot-designer .rpt-chip-filtered { flex-wrap: wrap; row-gap: var(--spacing-2); }
ui-analysis-pivot-designer .rpt-chip-filtered .rpt-chip-text { flex: 0 1 auto; max-width: calc(100% - 64px); }
ui-analysis-pivot-designer .rpt-chip-filterval { flex: 0 1 auto; min-width: 72px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 0 var(--spacing-3); color: var(--accent); background: var(--accent-dim); border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); border-radius: var(--radius-2xs); font-family: inherit; font-size: var(--fs-xs); line-height: var(--lh-loose); cursor: pointer; }
ui-analysis-pivot-designer .rpt-chip-filterval:hover { border-color: var(--accent); }
ui-analysis-pivot-designer .rpt-chip-filterval-incomplete { color: var(--danger); background: var(--danger-bg); border-color: var(--danger); }

ui-analysis-pivot-designer .rpt-bar { display: flex; align-items: center; gap: var(--spacing-5); min-height: 20px; }
/* One line, always — the "If you drop here:" preview text is longer than
   the resting sentence, and a wrap here grows .rpt-bar, which pushes the
   chart frame down under a stationary pointer mid-drag (contract §2.5.3). */
ui-analysis-pivot-designer .rpt-sentence { flex: 1; min-width: 0; font-size: var(--fs-lg); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ui-analysis-pivot-designer .rpt-sentence-preview { color: var(--accent); }
ui-analysis-pivot-designer .rpt-shape { margin-left: var(--spacing-5); color: var(--text-dim); font-size: var(--fs-sm); font-family: var(--mono); }
ui-analysis-pivot-designer .rpt-scope { color: var(--text-dim); font-size: var(--fs-sm); font-style: italic; }
ui-analysis-pivot-designer .rpt-show { display: flex; align-items: center; gap: var(--spacing-3); }
ui-analysis-pivot-designer .rpt-show-label { font-size: var(--fs-xs); font-weight: var(--fw-semibold); letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-dim); }
ui-analysis-pivot-designer .rpt-show-group { display: flex; border: 1px solid var(--border); border-radius: var(--radius-xs); overflow: hidden; }
ui-analysis-pivot-designer .rpt-show-btn { padding: var(--spacing-2) var(--spacing-4); color: var(--text-muted); background: var(--surface-2); border: none; border-right: 1px solid var(--border); font-family: inherit; font-size: var(--fs-sm); font-weight: var(--fw-medium); cursor: pointer; }
ui-analysis-pivot-designer .rpt-show-btn:last-child { border-right: none; }
ui-analysis-pivot-designer .rpt-show-btn-on { color: var(--text-on-accent); background: var(--accent); }

ui-analysis-pivot-designer .rpt-btn { padding: var(--spacing-3) var(--spacing-5); color: var(--text); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-xs); font-family: inherit; font-size: var(--fs-base); font-weight: var(--fw-medium); cursor: pointer; }
ui-analysis-pivot-designer .rpt-btn-primary { color: var(--text-on-accent); background: var(--accent); border-color: var(--accent); }
ui-analysis-pivot-designer .rpt-btn-quiet { color: var(--text-muted); background: none; }
ui-analysis-pivot-designer .rpt-btn-save { color: var(--success); background: none; border-color: transparent; }
ui-analysis-pivot-designer .rpt-btn-save:hover:not(:disabled) { background: var(--success-bg); }
ui-analysis-pivot-designer .rpt-btn-save:disabled { color: var(--text-dim); opacity: 0.5; cursor: not-allowed; }
ui-analysis-pivot-designer .rpt-iconbtn { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-xs); cursor: pointer; padding: var(--spacing-2) var(--spacing-4); color: var(--text-muted); font-size: var(--fs-lg); }
ui-analysis-pivot-designer .rpt-iconbtn:hover:not(:disabled) { color: var(--text); border-color: var(--surface-3); }
ui-analysis-pivot-designer .rpt-iconbtn:disabled { opacity: 0.4; cursor: not-allowed; }

ui-analysis-pivot-designer .rpt-save-error { padding: var(--spacing-4) var(--spacing-5); color: var(--danger); background: var(--danger-bg); border: 1px solid var(--danger); border-radius: var(--radius-xs); font-size: var(--fs-sm); line-height: var(--lh-normal); }
ui-analysis-pivot-designer .rpt-save-warning { padding: var(--spacing-4) var(--spacing-5); color: var(--warning); background: var(--warning-bg); border-radius: var(--radius-xs); font-size: var(--fs-sm); line-height: var(--lh-normal); }

ui-analysis-pivot-designer .rpt-result { min-width: 0; scrollbar-gutter: stable; overflow: auto; }
ui-analysis-pivot-designer .rpt-result.rpt-motion-clip { overflow: hidden; }
ui-analysis-pivot-designer .rpt-result-frame.rpt-result-stale { opacity: 0.55; }

/* FLIP motion (contract §2.7, rpt-motion.js). The pulse wash derives from
   the accent so it re-derives per brand instead of hardcoding lime. */
ui-analysis-pivot-table,
ui-analysis-pivot-designer {
  --rpt-motion-pulse: color-mix(in srgb, var(--accent) 22%, transparent);
}
ui-analysis-pivot-designer .rpt-empty { padding: var(--spacing-9); text-align: center; color: var(--text-dim); font-size: var(--fs-md); border: 1px dashed var(--border); border-radius: var(--radius-sm); }
ui-analysis-pivot-designer .rpt-notice { padding: var(--spacing-5) var(--spacing-6); font-size: var(--fs-base); line-height: var(--lh-normal); border-radius: var(--radius-xs); }
ui-analysis-pivot-designer .rpt-notice-error { color: var(--danger); background: var(--danger-bg); }

/* ui-analysis-pivot-chart — same tag in the Designer (interactive, tagged by
   ui-analysis-pivot-designer.js after render) and read-only on the report screen
   (contract §2.9). Self-scoped so both hosts get identical drawing. */
ui-analysis-pivot-chart { display: block; }
ui-analysis-pivot-chart {
  --rpt-chart-1: var(--chart-1);
  --rpt-chart-2: var(--chart-2);
  --rpt-chart-3: var(--chart-3);
  --rpt-chart-4: var(--chart-4);
  --rpt-chart-5: var(--chart-5);
  --rpt-chart-6: var(--chart-6);
}
ui-analysis-pivot-chart .rpt-chart-frame { position: relative; display: flex; flex-direction: column; gap: var(--spacing-4); }
ui-analysis-pivot-chart .rpt-chart-cap { font-size: var(--fs-sm); color: var(--text-muted); }
ui-analysis-pivot-chart .rpt-chart-body { position: relative; display: grid; grid-template-columns: auto minmax(0, 1fr); grid-template-rows: 1fr auto auto; gap: 0 var(--spacing-3); min-height: 200px; }
ui-analysis-pivot-chart .rpt-chart-yaxis { grid-column: 1; grid-row: 1; display: flex; flex-direction: column; justify-content: space-between; text-align: right; color: var(--text-dim); font-size: var(--fs-xs); font-family: var(--mono); }
ui-analysis-pivot-chart .rpt-chart-plot { grid-column: 2; grid-row: 1; display: flex; align-items: flex-end; gap: var(--spacing-5); padding-bottom: 1px; border-bottom: 1px solid var(--border); }
ui-analysis-pivot-chart .rpt-chart-group { flex: 1; display: flex; align-items: flex-end; justify-content: center; gap: var(--spacing-2); height: 180px; min-width: 0; }
ui-analysis-report-chart { display: block; }
ui-analysis-report-chart ui-analysis-pivot-chart .rpt-chart-group { height: var(--ui-analysis-report-chart-height, 180px); }
ui-analysis-pivot-chart .rpt-chart-barcol { flex: 1; max-width: 56px; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: var(--spacing-2); height: 100%; cursor: default; }
ui-analysis-pivot-chart .rpt-chart-barval { font-size: var(--fs-xs); font-family: var(--mono); color: var(--text-muted); }
/* transform-origin: bottom is what makes rpt-motion.js's scaleY grow/shrink
   from the baseline rather than the center (contract §2.7). */
ui-analysis-pivot-chart .rpt-chart-bar { display: block; width: 100%; min-height: 1px; border-radius: var(--radius-2xs) var(--radius-2xs) 0 0; transform-origin: bottom; }
ui-analysis-pivot-chart .rpt-chart-bar-gap { display: block; width: 60%; height: 0; border-top: 2px dashed var(--border); align-self: flex-end; }
ui-analysis-pivot-chart .rpt-chart-xaxis { grid-column: 2; grid-row: 2; display: flex; gap: var(--spacing-5); padding-top: var(--spacing-2); }
ui-analysis-pivot-chart .rpt-chart-catlabel { flex: 1; min-width: 0; text-align: center; font-size: var(--fs-sm); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
ui-analysis-pivot-chart .rpt-chart-blank { color: var(--text-dim); font-style: italic; }
ui-analysis-pivot-chart .rpt-chart-axisname { display: block; margin-top: var(--spacing-2); text-align: center; font-size: var(--fs-xs); color: var(--text-dim); font-style: normal; }
ui-analysis-pivot-chart .rpt-chart-body > .rpt-chart-axisname { grid-column: 2; grid-row: 3; }
ui-analysis-pivot-chart .rpt-chart-legend { display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-4); margin: 0 var(--spacing-3); padding: var(--spacing-3) var(--spacing-2); }
ui-analysis-pivot-chart .rpt-chart-legend-entry { display: flex; align-items: center; gap: var(--spacing-2); font-size: var(--fs-sm); color: var(--text); }
ui-analysis-pivot-chart .rpt-chart-legend-entry i { display: inline-block; width: 10px; height: 10px; border-radius: var(--radius-2xs); }
ui-analysis-pivot-chart .rpt-chart-notes { display: flex; flex-direction: column; gap: var(--spacing-2); }
ui-analysis-pivot-chart .rpt-chart-note { padding: var(--spacing-5) var(--spacing-6); color: var(--text-muted); font-size: var(--fs-base); line-height: var(--lh-normal); background: var(--surface-2); border-radius: var(--radius-xs); }

/* Designer-only: interactivity is wired onto this same markup from outside
   (ui-analysis-pivot-designer.js tags data-rpt-drag/data-flip after render), so the
   grab cursor and drop-slot overlay are scoped to that host, never to a
   read-only ui-analysis-pivot-chart on the report screen. */
ui-analysis-pivot-designer ui-analysis-pivot-chart [data-rpt-drag] { cursor: grab; }
ui-analysis-pivot-designer ui-analysis-pivot-chart [data-rpt-drag]:active { cursor: grabbing; }

ui-analysis-pivot-designer .rpt-guard { display: flex; flex-direction: column; gap: var(--spacing-4); padding: var(--spacing-6); background: var(--warning-bg); border: 1px solid var(--warning); border-radius: var(--radius-sm); }
ui-analysis-pivot-designer .rpt-guard-title { font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--warning); }
ui-analysis-pivot-designer .rpt-guard-text { font-size: var(--fs-base); line-height: var(--lh-normal); color: var(--text-muted); }
ui-analysis-pivot-designer .rpt-guard-actions { display: flex; align-items: center; gap: var(--spacing-4); flex-wrap: wrap; }

/* The table-shaped outline shown when there is no measure, or no row/column
   field yet (contract §2.8). Each box is also a zone drop target. */

/* Drag ghost and click-menu/filter popovers — appended to document.body, so
   these are deliberately NOT scoped under ui-analysis-pivot-designer. */
.rpt-ghost { position: fixed; z-index: var(--z-toast); pointer-events: none; transform: translate(10px, 10px); }
.rpt-ghost .rpt-chip { border-color: var(--accent); box-shadow: var(--elev-dropdown); cursor: grabbing; }
.rpt-ghost-cap { margin-top: 6px; max-width: 320px; padding: 5px 8px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xs); font-size: var(--fs-sm); color: var(--text); line-height: var(--lh-normal); }
.rpt-ghost-cap:empty { display: none; }
.rpt-ghost-cap-bad { color: var(--danger); border-color: var(--danger); }

.rpt-pop { position: absolute; z-index: var(--z-popover); min-width: 220px; background: var(--surface); border: 1px solid var(--surface-3); border-radius: var(--radius-sm); box-shadow: var(--elev-overlay); padding: var(--spacing-3); }
.rpt-pop-title { padding: var(--spacing-2) var(--spacing-4) var(--spacing-3); font-size: var(--fs-xs); letter-spacing: 0.6px; text-transform: uppercase; color: var(--text-dim); }
.rpt-pop-opt { display: block; width: 100%; text-align: left; background: none; border: none; padding: var(--spacing-3) var(--spacing-4); border-radius: var(--radius-xs); cursor: pointer; color: var(--text); font: inherit; }
.rpt-pop-opt:hover:not(:disabled) { background: var(--surface-2); }
.rpt-pop-opt small { display: block; color: var(--text-dim); font-size: var(--fs-sm); }
.rpt-pop-opt:disabled { opacity: 0.45; cursor: not-allowed; }
.rpt-pop-opt-danger { color: var(--danger); }
.rpt-filter-pop { min-width: 240px; }
.rpt-filter-pop select, .rpt-filter-pop input { width: 100%; background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-xs); padding: 5px 8px; font: inherit; margin-bottom: var(--spacing-3); }
.rpt-filter-pop input[type="checkbox"] { width: auto; margin: 0; }

.rpt-value-list { max-height: 260px; overflow-y: auto; margin-bottom: var(--spacing-3); }
.rpt-value { display: flex; align-items: center; gap: var(--spacing-3); padding: 3px 4px; border-radius: var(--radius-xs); cursor: pointer; }
.rpt-value:hover { background: var(--surface-2); }
.rpt-value-on { color: var(--text); }
.rpt-value-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rpt-value-blank-text { font-style: italic; color: var(--text-dim); }
.rpt-value-count { color: var(--text-dim); font-size: var(--fs-sm); font-family: var(--mono); }
.rpt-value-links { display: flex; gap: var(--spacing-3); margin-bottom: var(--spacing-3); }
.rpt-value-link { background: none; border: none; padding: 0; color: var(--accent); cursor: pointer; font-size: var(--fs-sm); }
.rpt-value-link:hover { text-decoration: underline; }
.rpt-filter-actions { display: flex; justify-content: flex-end; gap: var(--spacing-3); margin-top: var(--spacing-4); }
.rpt-filter-actions .rpt-btn { padding: var(--spacing-2) var(--spacing-5); color: var(--text); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-xs); font: inherit; font-size: var(--fs-sm); font-weight: var(--fw-medium); cursor: pointer; }
.rpt-filter-actions .rpt-btn-primary { color: var(--text-on-accent); background: var(--accent); border-color: var(--accent); }
.rpt-filter-actions .rpt-btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }
.rpt-filter-actions .rpt-filter-remove { margin-right: auto; color: var(--danger); background: none; border-color: transparent; }
.rpt-filter-actions .rpt-filter-remove:hover { background: var(--danger-bg); }
.rpt-filter-note { color: var(--text-muted); font-size: var(--fs-sm); line-height: var(--lh-normal); margin-bottom: var(--spacing-3); }

ui-analysis-layout-output { display: block; }
ui-analysis-layout-output .rpt-out { display: flex; flex-direction: column; gap: var(--spacing-5); padding: var(--spacing-5); }
ui-analysis-layout-output .rpt-out-empty { padding: var(--spacing-5); color: var(--text-dim); font-size: var(--fs-base); }
ui-analysis-layout-output .rpt-out-section { display: flex; flex-direction: column; gap: var(--spacing-3); }
ui-analysis-layout-output .rpt-out-h { margin: 0; font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em; }
ui-analysis-layout-output .rpt-out-dim { color: var(--text-dim); font-size: var(--fs-sm); }
ui-analysis-layout-output .rpt-out-mono { font-family: var(--mono); font-size: var(--fs-xs); }

ui-analysis-layout-output .rpt-out-table { border: 1px solid var(--border); border-radius: var(--radius-xs); overflow: hidden; }
ui-analysis-layout-output .rpt-out-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(90px, 1fr)); gap: var(--spacing-3); padding: var(--spacing-3) var(--spacing-4); border-bottom: 1px solid var(--border); align-items: start; }
ui-analysis-layout-output .rpt-out-row:last-child { border-bottom: none; }
ui-analysis-layout-output .rpt-out-row-head { background: var(--surface-2); font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-dim); text-transform: uppercase; }
ui-analysis-layout-output .rpt-out-cell { font-size: var(--fs-sm); color: var(--text); }

ui-analysis-layout-output .rpt-out-editor { grid-column: 1 / -1; padding: var(--spacing-4); background: var(--surface-2); border-top: 1px solid var(--accent); display: flex; flex-direction: column; gap: var(--spacing-3); }
ui-analysis-layout-output .rpt-editor-actions { display: flex; justify-content: flex-end; gap: var(--spacing-3); }

ui-analysis-layout-output .rpt-callout { padding: var(--spacing-4) var(--spacing-5); border-radius: var(--radius-xs); font-size: var(--fs-sm); line-height: var(--lh-normal); }
ui-analysis-layout-output .rpt-callout-info { color: var(--info); background: var(--info-bg); }

ui-analysis-layout-output .rpt-notice { padding: var(--spacing-4) var(--spacing-5); border-radius: var(--radius-xs); font-size: var(--fs-sm); line-height: var(--lh-normal); }
ui-analysis-layout-output .rpt-notice-muted { color: var(--text-muted); background: var(--surface-2); }
ui-analysis-layout-output .rpt-notice-error { color: var(--danger); background: var(--danger-bg); }
ui-analysis-layout-output .rpt-notice-warn { color: var(--warning); background: var(--warning-bg); }

ui-analysis-layout-output .rpt-chips { display: flex; gap: var(--spacing-2); flex-wrap: wrap; }
ui-analysis-layout-output .rpt-chip { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-xs); color: var(--text); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer; }
ui-analysis-layout-output .rpt-chip-on { border-color: var(--accent); color: var(--accent); }
ui-analysis-layout-output .rpt-chip-off { opacity: 0.55; text-decoration: line-through; }

ui-analysis-layout-output .rpt-disc { border: 1px solid var(--border); border-radius: var(--radius-xs); }
ui-analysis-layout-output .rpt-disc > summary { cursor: pointer; padding: var(--spacing-3) var(--spacing-4); font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-muted); }
ui-analysis-layout-output .rpt-disc-body { display: flex; flex-direction: column; gap: var(--spacing-2); padding: 0 var(--spacing-4) var(--spacing-4); }

ui-analysis-layout-output .rpt-fg { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--spacing-3); padding: 0 var(--spacing-4) var(--spacing-4); }
ui-analysis-layout-output .rpt-ff { display: flex; flex-direction: column; gap: var(--spacing-1); }
ui-analysis-layout-output .rpt-ff label { font-size: var(--fs-xs); color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.03em; }
ui-analysis-layout-output .rpt-input, ui-analysis-layout-output .rpt-select { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xs); font-family: inherit; }

ui-analysis-layout-output .rpt-btn { padding: var(--spacing-2) var(--spacing-4); font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-xs); cursor: pointer; }
ui-analysis-layout-output .rpt-btn-primary { color: var(--text-on-accent); background: var(--accent); border-color: var(--accent); }
ui-analysis-layout-output .rpt-btn-quiet { background: none; color: var(--text-muted); }
ui-analysis-layout-output .rpt-btn:disabled { opacity: 0.5; cursor: not-allowed; }

ui-analysis-layout-output .rpt-save-error { padding: var(--spacing-3) var(--spacing-4); color: var(--danger); background: var(--danger-bg); border-radius: var(--radius-xs); font-size: var(--fs-sm); }
ui-analysis-layout-output .rpt-savebar { display: flex; align-items: center; gap: var(--spacing-4); padding-top: var(--spacing-4); border-top: 1px solid var(--border); }

ui-analysis-pdf-preview { display: flex; flex: 1; min-width: 0; min-height: 0; width: 100%; }
ui-analysis-pdf-preview .rpt-pdf { display: flex; flex-direction: column; flex: 1; gap: var(--spacing-3); padding: var(--spacing-5); min-height: 480px; box-sizing: border-box; }
ui-analysis-pdf-preview .rpt-pdf-status { padding: var(--spacing-4) var(--spacing-5); border-radius: var(--radius-xs); font-size: var(--fs-base); line-height: var(--lh-normal); color: var(--text); background: var(--surface-2); }
ui-analysis-pdf-preview .rpt-pdf-status-busy { color: var(--info); background: var(--info-bg); }
ui-analysis-pdf-preview .rpt-pdf-status-error { color: var(--danger); background: var(--danger-bg); }
ui-analysis-pdf-preview .rpt-pdf-frame { flex: 1; min-height: 420px; width: 100%; border: 1px solid var(--border); border-radius: var(--radius-xs); background: var(--surface-2); }

/* A dataset report's own column presentation: stated alignment, the column the drawer is editing,
   and the drawer itself, which is appended to the designer's ui-detail-view. */
ui-analysis-pivot-table .pvt-table .pvt-align-left { text-align: left; }
ui-analysis-pivot-table .pvt-table .pvt-align-center { text-align: center; }
ui-analysis-pivot-table .pvt-table .pvt-align-right { text-align: right; }
ui-analysis-pivot-table [data-field] { cursor: pointer; }
ui-analysis-pivot-table [data-field][data-rpt-drag] { cursor: grab; }
ui-analysis-pivot-table .pvt-table .rpt-presenting { box-shadow: inset 0 -2px 0 var(--accent); }
ui-analysis-layout-output .rpt-out-row-action { cursor: pointer; }
ui-analysis-layout-output .rpt-out-row-action:hover { background: var(--surface-2); }
ui-analysis-layout-output .rpt-out-row-action:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
ui-analysis-layout-output .rpt-out-row.rpt-presenting { background: var(--accent-dim); }
ui-analysis-layout-output .rpt-out-owned { margin-left: var(--spacing-2); padding: 0 var(--spacing-2); font-size: var(--fs-xs); color: var(--accent); border: 1px solid var(--accent); border-radius: var(--radius-sm); }
.rpt-present { display: flex; flex-direction: column; gap: var(--spacing-5); }
.rpt-present-head { display: flex; flex-direction: column; gap: var(--spacing-1); }
.rpt-present-title { font-size: var(--fs-lg); font-weight: var(--fw-semibold); color: var(--text); }
.rpt-present-sub { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-dim); }
.rpt-present-field { display: flex; flex-direction: column; gap: var(--spacing-2); }
.rpt-present-label { font-size: var(--fs-xs); color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.03em; }
.rpt-present-hint, .rpt-present-note { font-size: var(--fs-sm); color: var(--text-dim); line-height: var(--lh-normal); }
.rpt-present-error { padding: var(--spacing-4) var(--spacing-5); color: var(--danger); background: var(--danger-bg); border-radius: var(--radius-xs); }
.rpt-present-foot { display: flex; flex-direction: column; align-items: flex-start; gap: var(--spacing-3); padding-top: var(--spacing-4); border-top: 1px solid var(--border); }
.rpt-present .rpt-input, .rpt-present .rpt-select { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-xs); font-family: inherit; }
.rpt-present .rpt-input:disabled { opacity: 0.5; }
.rpt-present .rpt-chips { display: flex; gap: var(--spacing-2); flex-wrap: wrap; }
.rpt-present .rpt-chip { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-xs); color: var(--text); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer; }
.rpt-present .rpt-chip-on { border-color: var(--accent); color: var(--accent); }
.rpt-present .rpt-chip-off { opacity: 0.55; text-decoration: line-through; }
.rpt-present .rpt-disc { border: 1px solid var(--border); border-radius: var(--radius-xs); }
.rpt-present .rpt-disc > summary { cursor: pointer; padding: var(--spacing-3) var(--spacing-4); font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-muted); }
.rpt-present .rpt-disc-body { display: flex; flex-direction: column; gap: var(--spacing-3); padding: 0 var(--spacing-4) var(--spacing-4); }
.rpt-present .rpt-btn { padding: var(--spacing-2) var(--spacing-4); font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-muted); background: none; border: 1px solid var(--border); border-radius: var(--radius-xs); cursor: pointer; }
.rpt-present .rpt-btn:disabled { opacity: 0.5; cursor: not-allowed; }

/* ══════════════════════════════════════════════════════════════════
   POLICY — the record-screen family
   ui-policy-workspace, ui-policy-header, ui-policy-facts,
   ui-policy-nav, ui-policy-card, ui-policy-fields, ui-policy-ledger,
   ui-policy-open-items, ui-policy-activity, ui-policy-history,
   ui-policy-notes, ui-policy-rail
   ══════════════════════════════════════════════════════════════════ */

/* Density is a scoped set of tokens, not a second stylesheet. Defaults sit on
   :root so a policy element authored OUTSIDE a workspace — in a plain
   ui-detail-view, or on the conformance page — is drawn at the same size as one
   inside it, instead of falling back to whatever it inherits. The workspace
   overrides them for its own subtree. */
/* TWO insets, not one, and the distinction is what the first cut got wrong.
   --policy-gutter is the CHROME inset: the header, the facts strip and the rail
   all sit on it, so their content shares one left edge down the screen.
   --policy-pad-x is the inset INSIDE a card, which is tighter — a card is
   already set apart by its border and a 16px inset inside a 12px gap reads as a
   box within a box. */
:root {
  --policy-row: 32px;
  --policy-fs: var(--fs-md);
  --policy-fs-label: var(--fs-sm);
  --policy-gutter: var(--spacing-7);
  --policy-gap: var(--spacing-6);
  --policy-pad-x: var(--spacing-6);
  --policy-pad-y: var(--spacing-4);
  --policy-nav-width: 208px;
  --policy-rail-width: 320px;
  --policy-field-columns: 4;
  --policy-when-width: 66px;
}

ui-policy-workspace[density='comfortable'] {
  --policy-row: 40px;
  --policy-fs: var(--fs-lg);
  --policy-fs-label: var(--fs-base);
  --policy-gutter: var(--spacing-8);
  --policy-gap: var(--spacing-7);
  --policy-pad-x: var(--spacing-7);
  --policy-pad-y: var(--spacing-5);
}

/* ── ui-policy-workspace layout ──────────────────────────────────────────────
   FOUR ROWS, ALWAYS, AND THE ROWS ARE NAMED RATHER THAN COUNTED. ui-detail-view
   reaches the same result with a :has() per combination of optional region, and
   the combinations multiply: header + facts + action bar + body is already four
   variants, and an unplaced item landing in the first free cell is how an
   authored ui-card once ate a 240px column there. An `auto` row holding no grid
   item is 0px with no row-gap, so the empty ones cost nothing and every region
   sits where the selector says regardless of which others are authored. */
ui-policy-workspace {
  display: grid;
  grid-template-columns: var(--detail-sidebar-width, var(--policy-nav-width)) minmax(0, 1fr);
  grid-template-rows: auto auto auto minmax(0, 1fr);
  flex: 1;
  min-height: 0;
  overflow: hidden;
  font-size: var(--policy-fs);
  transition: grid-template-columns 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

ui-policy-workspace:has(> ui-policy-rail) {
  grid-template-columns:
    var(--detail-sidebar-width, var(--policy-nav-width))
    minmax(0, 1fr)
    var(--policy-rail-width);
}

/* The COLUMN goes with the content. A rail withheld on this section leaves a
   reserved strip of nothing beside the work, which reads as a panel that failed
   to load rather than as one with nothing to say here. */
ui-policy-workspace:has(> ui-policy-rail[hidden]) {
  grid-template-columns:
    var(--detail-sidebar-width, var(--policy-nav-width))
    minmax(0, 1fr)
    0px;
}

ui-policy-workspace:not(:has(> :is(ui-policy-nav, ui-detail-sidebar))) {
  grid-template-columns: 0px minmax(0, 1fr);
}

ui-policy-workspace:not(:has(> :is(ui-policy-nav, ui-detail-sidebar))):has(> ui-policy-rail) {
  grid-template-columns: 0px minmax(0, 1fr) var(--policy-rail-width);
}

ui-policy-workspace.detail-sidebar-resizing {
  user-select: none;
  cursor: col-resize;
}

ui-policy-workspace > :is(ui-policy-header, ui-detail-hero, ui-policy-hero) {
  grid-area: 1 / 1 / 2 / -1;
}

ui-policy-workspace > ui-policy-facts {
  grid-area: 2 / 1 / 3 / -1;
}

ui-policy-workspace > ui-detail-actions {
  grid-area: 3 / 1 / 4 / -1;
}

ui-policy-workspace > :is(ui-policy-nav, ui-detail-sidebar) {
  grid-area: 4 / 1 / 5 / 2;
  padding: var(--policy-pad-y) 0;
  border-right: 1px solid var(--border);
  background: var(--surface);
  min-height: 0;
  overflow-y: auto;
  position: relative;
}

ui-policy-workspace > ui-detail-content {
  grid-area: 4 / 2 / 5 / 3;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
}

/* The rail's gutter and the gap between its cards are the MAIN COLUMN's, not a
   second number: 12 here against 16 there put two rhythms on one screen, and the
   two columns sit side by side. */
ui-policy-workspace > ui-policy-rail {
  grid-area: 4 / 3 / 5 / 4;
  display: flex;
  flex-direction: column;
  gap: var(--policy-gutter);
  padding: var(--policy-gutter);
  border-left: 1px solid var(--border);
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
}

ui-policy-workspace > ui-policy-rail[hidden] {
  display: none;
}

/* Same reasoning as the ui-detail-view catch-all: a stray at this level is
   reported by name on the console, and this only makes the failure a visible
   full-width band instead of an item silently eating the nav column. */
ui-policy-workspace > *:not(ui-policy-header, ui-detail-hero, ui-policy-hero,
  ui-policy-facts, ui-detail-actions, ui-policy-nav, ui-detail-sidebar,
  ui-detail-content, ui-policy-rail) {
  grid-column: 1 / -1;
}

ui-policy-workspace > :is(ui-policy-nav, ui-detail-sidebar)[loading] {
  opacity: 0.5;
  pointer-events: none;
}

/* ── ui-policy-header ───────────────────────────────────────────────────────── */

ui-policy-header {
  display: block;
  padding: var(--spacing-5) var(--policy-gutter);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

ui-policy-header .ph-row {
  display: flex;
  align-items: center;
  gap: var(--spacing-5);
  flex-wrap: wrap;
}

ui-policy-header .ph-title {
  margin: 0;
  font-size: var(--fs-3xl);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-tight);
  letter-spacing: -0.01em;
  color: var(--text);
}

/* Lighter and smaller on the SAME line: the record's own identifier is read
   with the name, not under it, and a second line here costs a row of the work. */
ui-policy-header .ph-subtitle {
  margin-left: var(--spacing-3);
  font-size: var(--fs-lg);
  font-weight: var(--fw-normal);
  color: var(--text-muted);
}

ui-policy-header .ph-gap {
  flex: 1;
  min-width: var(--spacing-5);
}

ui-policy-header .ph-status-slot:empty {
  display: none;
}

ui-policy-header .ph-status {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: var(--spacing-1) var(--spacing-5);
  border-radius: var(--radius-full);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  white-space: nowrap;
}

/* currentColor, so the disc is the status's own tone with nothing to keep in
   step: the .badge tone classes set `color` and the dot follows. */
ui-policy-header .ph-status-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-circle);
  background: currentColor;
  flex: none;
}

/* A NEUTRAL, not a transparent copy of the pill's tone. The suffix says how long
   the record has been in its status, which is not part of the status: sharing the
   tone at 80% opacity read as the same fact half-printed, and measured 3.94:1. */
ui-policy-header .ph-status-suffix {
  font-size: var(--fs-base);
  font-weight: var(--fw-normal);
  color: var(--text);
}

/* The shared .hero-chips rule right-aligns the chips and clears them from the
   row above, which is what a stacked hero's lower band needs and the opposite of
   what a single row does: right-aligned chips drifted to the middle of the header
   with a hole between them and the status they qualify. Left, inline, no
   clearance, and free to take the width a long server-computed reason needs. */
ui-policy-header .ph-chips {
  position: static;
  justify-content: flex-start;
  margin-top: 0;
  max-width: none;
  flex-shrink: 1;
  min-width: 0;
}

/* MUTED AND SMALL, beside the chips and not under them. The note is a secondary
   fact about the record — which version this is, when it was priced — and a
   chip-sized pill would read as another piece of state. :empty carries the
   hiding, so an absent note reserves nothing. */
ui-policy-header .ph-note {
  font-size: var(--fs-base);
  color: var(--text-muted);
  white-space: nowrap;
}

ui-policy-header .ph-note:empty {
  display: none;
}

ui-policy-header .ph-amount {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: var(--lh-tight);
  text-align: right;
}

ui-policy-header .ph-amount:empty {
  display: none;
}

ui-policy-header .ph-amount-value {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

/* The caption is where a figure says it is not final, so it is toned INFO
   rather than muted: muted reads as a footnote nobody has to take in, and
   "Estimated" is the one thing about this number a reader must not miss. */
ui-policy-header .ph-amount-caption {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--info);
}

ui-policy-header .hero-actions {
  position: static;
  gap: var(--spacing-4);
}

ui-policy-header .ph-more {
  gap: var(--spacing-2);
}

ui-policy-header .ph-more-caret {
  font-size: var(--fs-xs);
  line-height: var(--lh-none);
}

/* ── ui-hero-chip additions: the leading dot and the lighter suffix ────────── */

:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chip-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-circle);
  background: currentColor;
  flex: none;
}

/* Same reasoning as .ph-status-suffix, and an explicit size: <small> inherits
   0.833em, which off an 11px chip is 9.17px — below every step of the scale. */
:is(ui-detail-hero, ui-policy-hero, ui-policy-header) .hero-chip-suffix {
  font-size: var(--fs-sm);
  font-weight: var(--fw-normal);
  color: var(--text);
}

/* ── ui-policy-facts ───────────────────────────────────────────────────────── */

/* A BAND, NOT A ROW OF CARDS. The dividers are hairlines between facts rather
   than borders around them: a fact here is read once on arrival to place the
   record, and a box around each turns eight of them into eight things to look
   at. Scrolls sideways instead of wrapping, so the band is always one row high
   and the content under it does not move as the window narrows. */
ui-policy-facts {
  display: flex;
  margin: 0;
  padding: 0;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  scrollbar-width: none;
  font-size: var(--policy-fs);
}

ui-policy-facts::-webkit-scrollbar {
  display: none;
}

ui-policy-facts .pf-fact {
  padding: var(--spacing-3) var(--policy-gutter);
  border-right: 1px solid var(--border);
  white-space: nowrap;
  flex: none;
}

ui-policy-facts .pf-fact:last-child {
  border-right: none;
}

ui-policy-facts .pf-label {
  margin: 0;
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-normal);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

ui-policy-facts .pf-value {
  margin: 0;
  font-weight: var(--fw-medium);
  line-height: var(--lh-normal);
  color: var(--text);
}

/* --text-muted, not --text-dim. The missing text is a STATEMENT about the value
   ("Not entered"), so it has to be readable; --text-dim measures 2.41:1 here,
   under the 4.5:1 floor, and rendered it as a smudge where the em-dash it
   replaces was at least unambiguous. Italic carries the "this is not the value"
   reading instead. */
ui-policy-facts .pf-missing,
ui-policy-fields .pl-missing {
  color: var(--text-muted);
  font-weight: var(--fw-normal);
  font-style: italic;
}

ui-policy-facts .pf-link {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: var(--accent);
  cursor: pointer;
}

ui-policy-facts .pf-link:hover {
  text-decoration: underline;
}

ui-policy-facts .pf-dot-item {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-2);
  margin-right: var(--spacing-3);
  font-weight: var(--fw-normal);
}

ui-policy-facts .pf-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-circle);
  background: currentColor;
  flex: none;
}

ui-policy-facts .pf-dot-item {
  color: var(--text);
}

ui-policy-facts .pf-tone-success .pf-dot { color: var(--success); }
ui-policy-facts .pf-tone-warning .pf-dot { color: var(--warning); }
ui-policy-facts .pf-tone-danger .pf-dot { color: var(--danger); }
ui-policy-facts .pf-tone-info .pf-dot { color: var(--info); }
ui-policy-facts .pf-tone-muted .pf-dot { color: var(--text-muted); }

/* ── ui-policy-nav ─────────────────────────────────────────────────────────── */

/* The .sb-* geometry is ui-detail-sidebar's, reached through the shared
   :is(ui-detail-sidebar, ui-policy-nav) selectors above. Only the differences
   are here: no group chevron, sentence-case group labels, an item row at the
   family's own height, and the active mark as a left accent bar with a tint. */
ui-policy-nav {
  display: block;
  font-size: var(--policy-fs);
}

ui-policy-nav .sb-section + .sb-section {
  margin-top: var(--spacing-4);
}

ui-policy-nav .sb-section-header {
  padding: var(--spacing-5) var(--policy-gutter) var(--spacing-2);
  cursor: default;
}

ui-policy-nav .sb-section-header:hover {
  background: transparent;
}

ui-policy-nav .sb-section-label {
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-semibold);
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-muted);
}

ui-policy-nav .pn-item {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  height: var(--policy-row);
  margin: 0;
  padding: 0 var(--policy-gap) 0 var(--policy-gutter);
  border-left: 3px solid transparent;
  border-radius: 0;
  color: var(--text-muted);
  background: transparent;
  cursor: pointer;
}

ui-policy-nav .pn-item:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* A left bar and a tint, not a filled pill: the bar marks the edge of the rail
   the eye is already following down, and a solid accent fill here would compete
   with the one primary verb in the header two rows up. */
ui-policy-nav .pn-item.active {
  border-left-color: var(--accent);
  background: var(--accent-dim);
  color: var(--text);
  font-weight: var(--fw-semibold);
}

ui-policy-nav .pn-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* PLAIN TEXT, NOT A PILL. On a rail of ten sections ten pills read as ten
   badges wanting attention, and the number behind "Drivers" is an inventory. */
ui-policy-nav .pn-count {
  flex: none;
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-normal);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

ui-policy-nav .sb-state-legend {
  margin: var(--spacing-7) var(--policy-gutter);
}

/* ── ui-policy-card ────────────────────────────────────────────────────────── */

ui-policy-card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  font-size: var(--policy-fs);
}

/* No adjacent-sibling margin. The rail is a flex column with a gap and a panel
   authors a <ui-stack gap>, so a margin here stacked with both and produced two
   different gaps on one screen depending on which container a card sat in. */

ui-policy-card .pc-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: var(--spacing-4) var(--policy-pad-x);
  border-bottom: 1px solid var(--border);
}

ui-policy-card .pc-title {
  margin: 0;
  font-size: var(--policy-fs);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  color: var(--text);
}

ui-policy-card .pc-count {
  font-size: var(--policy-fs);
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
}

ui-policy-card .pc-head-gap {
  flex: 1;
}

ui-policy-card .pc-badge,
ui-policy-ledger .pg-badge {
  padding: 0 var(--spacing-3);
  border-radius: var(--radius-xs);
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
  background: var(--surface-3);
  white-space: nowrap;
}

ui-policy-card .pc-badge.info,
ui-policy-ledger .pg-badge.info { color: var(--info); background: var(--info-bg); }
ui-policy-card .pc-badge.success,
ui-policy-ledger .pg-badge.success { color: var(--success); background: var(--success-bg); }
ui-policy-card .pc-badge.warning,
ui-policy-ledger .pg-badge.warning { color: var(--warning); background: var(--warning-bg); }
ui-policy-card .pc-badge.danger,
ui-policy-ledger .pg-badge.danger { color: var(--danger); background: var(--danger-bg); }
ui-policy-card .pc-badge.muted,
ui-policy-ledger .pg-badge.muted { color: var(--text-muted); background: var(--surface-3); }

ui-policy-card .pc-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-5);
}

/* A LINK, NOT A BUTTON. It moves the reader rather than changing the record,
   and a bordered control in every card header competes with the single primary
   verb in the record header above it. */
ui-policy-card .pc-action {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--policy-fs-label);
  color: var(--accent);
  cursor: pointer;
}

ui-policy-card .pc-action:hover {
  text-decoration: underline;
}

ui-policy-card .pc-action-dead {
  font-size: var(--policy-fs-label);
  color: var(--text-dim);
}

ui-policy-card .pc-hint {
  padding: 0 var(--spacing-2);
  border: 1px solid var(--border);
  border-bottom-width: 2px;
  border-radius: var(--radius-xs);
  font-family: var(--mono);
  font-size: var(--fs-sm);
  color: var(--text-muted);
  background: var(--surface-2);
  text-decoration: none;
}

ui-policy-card .pc-body {
  padding: var(--spacing-5) var(--policy-pad-x);
}

/* A table fills the card edge to edge; its own cell padding is the inset. */
ui-policy-card .pc-body:has(> ui-data-table) {
  padding: 0;
}

ui-policy-card .pc-empty {
  display: none;
}

/* ONE LINE, INSIDE THE SAME BORDER. A section with nothing in it yet is still a
   section, and an illustrated empty box costs the screen of three rows of the
   thing itself. */
ui-policy-card.pc-is-empty .pc-empty {
  display: block;
  padding: var(--spacing-5) var(--policy-pad-x);
  font-size: var(--policy-fs);
  color: var(--text-muted);
}

ui-policy-card.pc-is-empty .pc-body {
  display: none;
}

/* ── ui-policy-fields ──────────────────────────────────────────────────────── */

ui-policy-fields {
  display: grid;
  grid-template-columns: repeat(var(--policy-field-columns), minmax(0, 1fr));
  gap: var(--spacing-5) var(--spacing-7);
  margin: 0;
  font-size: var(--policy-fs);
}

@media (max-width: 1100px) {
  ui-policy-fields {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  ui-policy-fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

ui-policy-fields .pl-field {
  min-width: 0;
}

ui-policy-fields .pl-label {
  margin: 0;
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-normal);
  line-height: var(--lh-normal);
  color: var(--text-muted);
}

/* WRAPS, and that is the point of the grid: a value truncated behind an
   ellipsis is a value the reader has to hover to read, and an address is the
   commonest value in this grid. */
ui-policy-fields .pl-value {
  margin: 0;
  font-weight: var(--fw-medium);
  line-height: var(--lh-normal);
  color: var(--text);
  overflow-wrap: anywhere;
}

/* ── ui-policy-ledger ──────────────────────────────────────────────────────── */

ui-policy-ledger {
  display: block;
  font-size: var(--policy-fs);
}

ui-policy-ledger .pg-head {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  margin-bottom: var(--spacing-4);
}

ui-policy-ledger .pg-title {
  margin: 0;
  font-size: var(--policy-fs);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-normal);
  color: var(--text);
}

/* min-height, not height. A filed discount name runs to two lines at this width
   and a fixed 32px row let the label overlap the row under it by 7px — the label
   grew, the box did not. The padding keeps a one-line row on the same 32px pitch
   as the tables beside it. */
ui-policy-ledger .pg-line {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-5);
  min-height: var(--policy-row);
  padding: var(--spacing-3) 0;
  border-bottom: 1px solid var(--border);
}

ui-policy-ledger .pg-line:last-child {
  border-bottom: none;
}

ui-policy-ledger .pg-label {
  flex: 1;
  min-width: 0;
  color: var(--text);
  align-self: center;
  line-height: var(--lh-normal);
}

/* Tabular figures and a right edge, because a column of amounts is compared
   digit by digit and a proportional 1 is narrower than a proportional 8. */
ui-policy-ledger .pg-amount {
  flex: none;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text);
  align-self: center;
}

ui-policy-ledger .pg-total .pg-label,
ui-policy-ledger .pg-total .pg-amount {
  font-weight: var(--fw-semibold);
}

ui-policy-ledger .pg-meta .pg-label,
ui-policy-ledger .pg-meta .pg-amount {
  color: var(--text-muted);
}

/* The meta row is the one line whose "amount" is a sentence, not a figure:
   long text on the right and a short fixed word on the left — the opposite
   of every other row, so the two sides swap which one is allowed to shrink. */
ui-policy-ledger .pg-meta .pg-label {
  flex: none;
}
ui-policy-ledger .pg-meta .pg-amount {
  flex: 1;
  min-width: 0;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-policy-ledger .pg-note {
  margin: var(--spacing-4) 0 0;
  font-size: var(--policy-fs-label);
  line-height: var(--lh-relaxed);
  color: var(--text-muted);
}

ui-policy-ledger .pg-empty,
ui-policy-open-items .po-empty,
ui-policy-activity .pa-empty {
  padding: var(--spacing-4) 0;
  font-size: var(--policy-fs);
  color: var(--text-muted);
}

/* ── ui-policy-open-items ──────────────────────────────────────────────────── */

ui-policy-open-items {
  display: block;
  font-size: var(--policy-fs);
}

/* UPPERCASE HERE AND NOWHERE ELSE IN THE FAMILY. A category is a severity band
   — "before bind" against "for information" — and it has to be distinguishable
   at a glance from the sentences under it, which is exactly what the fact and
   field labels must NOT do. */
ui-policy-open-items .po-category {
  padding: var(--spacing-3) 0 var(--spacing-1);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

ui-policy-open-items .po-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

ui-policy-open-items .po-item {
  display: flex;
  gap: var(--spacing-5);
  padding: var(--spacing-4) 0;
  border-bottom: 1px solid var(--border);
}

ui-policy-open-items .po-group:last-child .po-item:last-child {
  border-bottom: none;
}

ui-policy-open-items .po-icon {
  flex: none;
  display: inline-flex;
  margin-top: var(--spacing-1);
}

ui-policy-open-items .po-icon svg {
  display: block;
  width: 18px;
  height: 18px;
}

/* The disc takes the tone and the mark is punched out of it in the surface
   color, so the glyph re-themes with the brand. An SVG presentation attribute
   would lose to these rules, which is why the element emits none. */
ui-policy-open-items .po-disc {
  fill: currentColor;
}

ui-policy-open-items .po-mark {
  fill: none;
  stroke: var(--surface);
  stroke-width: 2.4;
  stroke-linecap: round;
}

ui-policy-open-items .po-group-danger .po-category { color: var(--danger); }
ui-policy-open-items .po-group-warning .po-category { color: var(--warning); }
ui-policy-open-items .po-group-success .po-category { color: var(--success); }
ui-policy-open-items .po-group-info .po-category { color: var(--info); }
ui-policy-open-items .po-group-muted .po-category { color: var(--text-muted); }

ui-policy-open-items .po-tone-danger { color: var(--danger); }
ui-policy-open-items .po-tone-warning { color: var(--warning); }
ui-policy-open-items .po-tone-success { color: var(--success); }
ui-policy-open-items .po-tone-info { color: var(--info); }
ui-policy-open-items .po-tone-muted { color: var(--text-muted); }

ui-policy-open-items .po-body {
  display: block;
  min-width: 0;
}

ui-policy-open-items .po-text {
  display: block;
  line-height: var(--lh-relaxed);
  color: var(--text);
}

ui-policy-open-items .po-fix {
  display: inline-block;
  margin-top: var(--spacing-2);
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-semibold);
  color: var(--accent);
  cursor: pointer;
}

ui-policy-open-items .po-fix:hover {
  text-decoration: underline;
}

/* ── ui-policy-activity ────────────────────────────────────────────────────── */

ui-policy-activity {
  display: block;
  font-size: var(--policy-fs);
}

ui-policy-activity .pa-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

ui-policy-activity .pa-item {
  display: flex;
  gap: var(--spacing-5);
  padding: var(--spacing-2) 0;
  line-height: var(--lh-relaxed);
}

/* FIXED WIDTH, so every sentence starts on one left edge: a reader scanning a
   history is reading the text down the page, not the dates. */
ui-policy-activity .pa-when {
  flex: none;
  width: var(--policy-when-width);
  font-size: var(--policy-fs-label);
  color: var(--text-muted);
}

ui-policy-activity .pa-text {
  min-width: 0;
  color: var(--text);
}

/* ── ui-policy-history ─────────────────────────────────────────────────────── */

ui-policy-history {
  display: block;
  font-size: var(--policy-fs);
}

ui-policy-history .phy-list,
ui-policy-history .phy-change-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* CAPPED. A diff is read across, label to arrow to value, and three tracks
   spread over a full-width section put the before and the after at opposite
   ends of the screen. The cap is the block, not the card. */
ui-policy-history .phy-change-list {
  max-width: 640px;
}

ui-policy-history .phy-item {
  display: flex;
  gap: var(--spacing-5);
  padding: var(--spacing-4) 0;
  border-bottom: 1px solid var(--border);
  line-height: var(--lh-relaxed);
}

ui-policy-history .phy-item:last-child {
  border-bottom: none;
}

/* The SAME fixed column as ui-policy-activity, off the same token: the rail's
   short list and this long one sit on one screen in the concept, and two date
   columns of different widths read as two unrelated lists. */
ui-policy-history .phy-when {
  flex: none;
  width: var(--policy-when-width);
  font-size: var(--policy-fs-label);
  color: var(--text-muted);
}

ui-policy-history .phy-body {
  min-width: 0;
  flex: 1;
}

ui-policy-history .phy-text {
  margin: 0;
  color: var(--text);
}

ui-policy-history .phy-actor {
  font-weight: var(--fw-semibold);
}

ui-policy-history .phy-summary {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-3);
  margin-top: var(--spacing-2);
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-semibold);
  color: var(--accent);
  cursor: pointer;
  list-style: none;
  width: fit-content;
}

ui-policy-history .phy-summary::-webkit-details-marker {
  display: none;
}

ui-policy-history .phy-summary:hover {
  text-decoration: underline;
}

/* A PLAIN NUMBER, the way ui-policy-card's header count is: a pill here would
   be a third weight of emphasis inside one row of a list. */
ui-policy-history .phy-change-count {
  font-weight: var(--fw-normal);
  color: var(--text-muted);
}

/* THREE COLUMNS AND THE ARROW, not a table: each change is one line of the form
   "label  before → after", and the label column is fixed so a row of six
   changes lines its arrows up and the diff is read down the page. */
ui-policy-history .phy-change {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) auto minmax(0, 1fr);
  gap: var(--spacing-4);
  padding: var(--spacing-2) 0;
  font-size: var(--policy-fs-label);
  align-items: baseline;
}

ui-policy-history .phy-change-label {
  color: var(--text-muted);
}

/* STRUCK THROUGH AND MUTED on the way out, plain on the way in. The arrow says
   the direction and the weight says which value is the record's now — a diff
   whose two sides look identical makes the reader re-read the arrow. */
ui-policy-history .phy-before {
  color: var(--text-muted);
  text-decoration: line-through;
}

ui-policy-history .phy-after {
  color: var(--text);
  font-weight: var(--fw-semibold);
}

ui-policy-history .phy-arrow {
  color: var(--text-muted);
}

/* No strike on a side that HAS no value. The decoration is declared on
   .phy-before, so this has to win on the same element, not on a child. */
ui-policy-history .phy-side-missing {
  color: var(--text-muted);
  font-style: italic;
  text-decoration: none;
}

ui-policy-history .phy-empty {
  padding: var(--spacing-4) 0;
  font-size: var(--policy-fs);
  color: var(--text-muted);
}

/* ── ui-policy-notes ───────────────────────────────────────────────────────── */

ui-policy-notes {
  display: block;
  font-size: var(--policy-fs);
}

ui-policy-notes .pnt-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

ui-policy-notes .pnt-item {
  padding: var(--spacing-4) 0;
  border-bottom: 1px solid var(--border);
  line-height: var(--lh-relaxed);
}

ui-policy-notes .pnt-item:last-child {
  border-bottom: none;
}

ui-policy-notes .pnt-head {
  display: flex;
  align-items: baseline;
  gap: var(--spacing-3);
}

ui-policy-notes .pnt-author {
  font-weight: var(--fw-semibold);
  color: var(--text);
}

ui-policy-notes .pnt-when {
  font-size: var(--policy-fs-label);
  color: var(--text-muted);
}

ui-policy-notes .pnt-head-gap {
  flex: 1;
  min-width: var(--spacing-3);
}

/* MUTED UNTIL IT IS WANTED, not hidden until it is hovered. A column of red
   Delete links is the loudest thing in the card, and a control that only exists
   on hover does not exist on a touch screen at all. */
ui-policy-notes .pnt-delete {
  flex: none;
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-semibold);
  color: var(--text-muted);
  cursor: pointer;
}

ui-policy-notes .pnt-item:hover .pnt-delete,
ui-policy-notes .pnt-delete:hover,
ui-policy-notes .pnt-delete:focus-visible {
  color: var(--danger);
}

ui-policy-notes .pnt-body {
  margin: 0;
  color: var(--text);
  white-space: pre-line;
}

ui-policy-notes .pnt-empty {
  padding: var(--spacing-4) 0;
  font-size: var(--policy-fs);
  color: var(--text-muted);
}

ui-policy-notes .pnt-compose {
  padding-top: var(--spacing-4);
}

/* The rule between the notes and the composer belongs to the LIST, not to the
   composer: on a record with no notes yet the composer is the whole card, and a
   border above it there is a line under nothing. */
ui-policy-notes .pnt-list-region:not(:empty) {
  display: block;
  border-bottom: 1px solid var(--border);
}

ui-policy-notes .pnt-input {
  display: block;
  width: 100%;
  min-height: 52px;
  padding: var(--spacing-3) var(--spacing-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--policy-fs);
  line-height: var(--lh-relaxed);
  resize: vertical;
}

ui-policy-notes .pnt-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

ui-policy-notes .pnt-input:disabled {
  opacity: 0.6;
  cursor: progress;
}

ui-policy-notes .pnt-compose-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--spacing-3);
}

/* --text-on-accent, NEVER a literal white: --accent is a carrier token and
   Trident's is lime at 0.85 luminance, where white text measures 1.2:1. */
ui-policy-notes .pnt-submit {
  padding: var(--spacing-2) var(--spacing-5);
  border: none;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: var(--text-on-accent);
  font: inherit;
  font-size: var(--policy-fs-label);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

ui-policy-notes .pnt-submit:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

ui-policy-notes .pnt-error {
  margin-top: var(--spacing-3);
  padding: var(--spacing-3) var(--spacing-4);
  border-radius: var(--radius-sm);
  background: var(--danger-bg);
  color: var(--danger);
  font-size: var(--policy-fs-label);
}

/* ── ui-data-table — the link cell and the inline tag ──────────────────────── */

ui-data-table .dt-link {
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: var(--accent);
  text-decoration: none;
  cursor: pointer;
}

ui-data-table .dt-link:hover {
  text-decoration: underline;
}

/* A SUFFIX, so it takes a left margin and never a column of its own: it
   qualifies the value beside it, and given a column it would be a mostly empty
   one with a heading nobody can word. */
ui-data-table .dt-tag {
  display: inline-block;
  margin-left: var(--spacing-3);
  padding: 0 var(--spacing-3);
  border-radius: var(--radius-xs);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-relaxed);
  color: var(--text);
  background: var(--surface-3);
  vertical-align: baseline;
  white-space: nowrap;
}

/* ── compact density inside a workspace ────────────────────────────────────── */

ui-policy-workspace ui-data-table {
  font-size: var(--policy-fs);
}

ui-policy-workspace ui-data-table .dt-td {
  height: var(--policy-row);
  font-size: var(--policy-fs);
}

/* SENTENCE CASE, and it applies to a table in a ui-policy-card anywhere — not
   only inside a workspace. A column heading here is a phrase a reader reads
   ("Yrs lic.", "Incidents (36 mo)"), and uppercasing it costs the word shapes
   that make it legible at 11px. */
:is(ui-policy-workspace, ui-policy-card) ui-data-table .dt-th {
  font-size: var(--policy-fs-label);
  letter-spacing: normal;
  text-transform: none;
}

/* THE PANEL CONTRIBUTES NOTHING. A ui-content-panel in a workspace is a region
   boundary, not a box: its own inset stacked with the <ui-stack padding> every
   panel already authors for 28px of gutter on each side of a card, which is the
   blank band this rule exists to remove. One gutter, and the panel's content
   decides it — which is also why a panel reused from another screen lands here
   with the spacing it was authored with instead of gaining a second frame. */
ui-policy-workspace ui-content-panel {
  padding: 0;
  gap: 0;
  background: transparent;
  border: 0;
}

ui-analysis-parameter-designer { display: block; padding: var(--spacing-5); }
ui-analysis-parameter-designer .apm-lede { padding: var(--spacing-4) var(--spacing-5); margin-bottom: var(--spacing-5); font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--text-muted); background: var(--info-bg); border-radius: var(--radius-xs); }
ui-analysis-parameter-designer .apm-section-h { margin: var(--spacing-6) 0 var(--spacing-3); font-size: var(--fs-xs); font-weight: var(--fw-semibold); letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-dim); }
ui-analysis-parameter-designer .apm-list { display: flex; flex-direction: column; gap: var(--spacing-3); }
ui-analysis-parameter-designer .apm-row { display: flex; flex-direction: column; gap: var(--spacing-3); padding: var(--spacing-4); background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-xs); }
ui-analysis-parameter-designer .apm-row-fixed { background: var(--warning-bg); border-color: var(--warning); }
ui-analysis-parameter-designer .apm-row-head { display: flex; align-items: baseline; gap: var(--spacing-3); flex-wrap: wrap; }
ui-analysis-parameter-designer .apm-row-pos { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; font-size: var(--fs-xs); color: var(--text-dim); background: var(--surface-3); border-radius: var(--radius-full); }
ui-analysis-parameter-designer .apm-row-label { font-weight: var(--fw-semibold); }
ui-analysis-parameter-designer .apm-row-op { font-size: var(--fs-sm); color: var(--text-muted); }
ui-analysis-parameter-designer .apm-row-value { font-size: var(--fs-sm); color: var(--text); }
ui-analysis-parameter-designer .apm-row-control { display: flex; align-items: baseline; gap: var(--spacing-3); }
ui-analysis-parameter-designer .apm-row-reason { font-family: var(--mono); font-size: var(--fs-xs); color: var(--text-dim); }
ui-analysis-parameter-designer .apm-row-opts { display: flex; align-items: center; gap: var(--spacing-4); flex-wrap: wrap; }
ui-analysis-parameter-designer .apm-spacer { flex: 1; }
ui-analysis-parameter-designer .apm-tag { padding: 1px var(--spacing-3); font-size: var(--fs-xs); color: var(--info); border: 1px solid var(--info); border-radius: var(--radius-full); }
ui-analysis-parameter-designer .apm-tag-fixed { color: var(--warning); border-color: var(--warning); }
ui-analysis-parameter-designer .apm-chk { display: inline-flex; align-items: center; gap: var(--spacing-2); font-size: var(--fs-sm); color: var(--text-muted); cursor: pointer; }
ui-analysis-parameter-designer .apm-note { font-size: var(--fs-sm); color: var(--text-dim); line-height: var(--lh-normal); }
ui-analysis-parameter-designer .apm-warn { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--text-muted); background: var(--warning-bg); border-radius: var(--radius-xs); }
ui-analysis-parameter-designer .apm-hint-input { width: 100%; padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-xs); box-sizing: border-box; }
ui-analysis-parameter-designer .apm-move { padding: 0 var(--spacing-3); color: var(--text-muted); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-xs); cursor: pointer; }
ui-analysis-parameter-designer .apm-move:disabled { opacity: 0.4; cursor: not-allowed; }
ui-analysis-parameter-designer .apm-empty { padding: var(--spacing-5); font-size: var(--fs-sm); font-style: italic; line-height: var(--lh-normal); color: var(--text-dim); border: 1px dashed var(--border); border-radius: var(--radius-xs); }
ui-analysis-parameter-designer .apm-hint { font-size: var(--fs-sm); color: var(--text-dim); }
ui-analysis-parameter-designer .apm-error { padding: var(--spacing-3) var(--spacing-4); font-size: var(--fs-sm); color: var(--danger); background: var(--danger-bg); border-radius: var(--radius-xs); }
ui-analysis-parameter-designer .apm-footer { display: flex; align-items: center; gap: var(--spacing-4); margin-top: var(--spacing-6); padding-top: var(--spacing-4); border-top: 1px solid var(--border); }
ui-analysis-parameter-designer .apm-save { padding: var(--spacing-2) var(--spacing-5); font-weight: var(--fw-semibold); color: var(--text-on-accent); background: var(--accent); border: 1px solid var(--accent); border-radius: var(--radius-xs); cursor: pointer; }
ui-analysis-parameter-designer .apm-save:disabled { opacity: 0.5; cursor: not-allowed; }

ui-analysis-run-form { display: block; }
ui-analysis-run-form .arf-hint { font-size: var(--fs-sm); line-height: var(--lh-normal); color: var(--text-dim); }
ui-analysis-run-form .arf-error { padding: var(--spacing-3) var(--spacing-4); font-size: var(--fs-sm); color: var(--danger); background: var(--danger-bg); border-radius: var(--radius-xs); }
ui-analysis-run-form .arf-added-note { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--spacing-3); margin-top: calc(var(--spacing-2) * -1); }
ui-analysis-run-form .arf-link { padding: 0; font-size: var(--fs-sm); color: var(--info); background: none; border: none; text-decoration: underline; cursor: pointer; }
ui-analysis-run-form .arf-more { margin-top: var(--spacing-4); padding-top: var(--spacing-4); border-top: 1px solid var(--border); }
ui-analysis-run-form .arf-more-toggle { padding: 0; font-size: var(--fs-sm); color: var(--text-muted); background: none; border: none; cursor: pointer; }
ui-analysis-run-form .arf-more-body { display: flex; flex-direction: column; gap: var(--spacing-3); margin-top: var(--spacing-3); }
ui-analysis-run-form .arf-more-select { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-xs); }
ui-analysis-parameter-designer .apm-add { margin-top: var(--spacing-4); }
ui-analysis-parameter-designer .apm-add-select { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); color: var(--text); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-xs); }
ui-analysis-parameter-designer .apm-link { padding: 0; font-size: var(--fs-sm); color: var(--info); background: none; border: none; text-decoration: underline; cursor: pointer; }
ui-analysis-parameter-designer .apm-row-op-select { padding: 1px var(--spacing-3); font-size: var(--fs-sm); color: var(--text-muted); background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-xs); }

/* The popover mounts on document.body, so every selector here is unscoped — an
   ui-analysis-pivot-designer prefix matches nothing. */
/* Auto-width rather than a full-width form control, at the chip label's size. */
.rpt-filter-pop select { width: auto; max-width: 100%; padding: 1px var(--spacing-3); font-size: var(--fs-base); line-height: var(--lh-loose); }
.rpt-filter-pop input[type="text"], .rpt-filter-pop input[type="number"], .rpt-filter-pop input[type="date"] { padding: var(--spacing-2) var(--spacing-3); font-size: var(--fs-sm); }
.rpt-filter-pop .rpt-chips-entry { width: 100%; }
.rpt-filter-pop .rpt-chips { display: flex; flex-wrap: wrap; gap: var(--spacing-2); margin-bottom: var(--spacing-3); }
.rpt-filter-pop .rpt-chip-value { display: inline-flex; align-items: center; gap: var(--spacing-2); padding: 1px var(--spacing-3); font-size: var(--fs-sm); color: var(--text); background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--radius-xs); }
.rpt-filter-pop .rpt-chip-value-x { padding: 0; color: var(--text-dim); background: none; border: none; font-size: var(--fs-base); line-height: var(--lh-none); cursor: pointer; }
.rpt-filter-pop .rpt-chip-value-x:hover { color: var(--danger); }
.rpt-filter-pop .rpt-chk { display: inline-flex; align-items: center; gap: var(--spacing-2); margin-top: var(--spacing-2); font-size: var(--fs-sm); color: var(--text-muted); cursor: pointer; }
