/**
 * 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;
  /* Must exist in the BASE scope, not only in :root[theme='light'].
     It is consumed unconditionally (.vs-segment[aria-pressed="true"] sets
     color: var(--text-on-accent) on a var(--accent) background), so defining
     it only for the light theme left it resolving to EMPTY in dark mode — the
     default — and the text fell back to whatever it inherited.
     Found by tools/sdk-test.mjs `tokens-resolved`.
     NOTE: white on --accent measures 4.02:1 in dark mode, which fails WCAG AA
     for normal text. Raising that is ND-101's scope; this only stops the
     value being empty. */
  --text-on-accent: #ffffff;
  --text-muted: #8b8fa5;
  --text-dim: #5c6078;
  --accent: #7c6bf0;
  --accent-dim: rgba(124, 107, 240, 0.15);
  /* 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: #3b82f6;
  --info-bg: rgba(59, 130, 246, 0.12);
  --warning: #f59e0b;
  --warning-bg: rgba(245, 158, 11, 0.12);
  --success: #22c55e;
  --success-bg: rgba(34, 197, 94, 0.12);
  --danger: #ef4444;
  --danger-bg: rgba(239, 68, 68, 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 colour 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 tokenised: 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 colours. */
  --chart-1: var(--accent);
  --chart-2: #3b82f6;
  --chart-3: #22c55e;
  --chart-4: #f59e0b;
  --chart-5: #ef4444;
  --chart-6: #06b6d4;
  --chart-7: #ec4899;
  --chart-8: #8b5cf6;
  --chart-9: #14b8a6;
  --chart-10: #f97316;

  /* ── 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;
  --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 formalises 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 neighbouring 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 tokenised, while colour 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 COLOUR 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
     neighbouring 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 */
  --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 colour 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-muted: #6b7280;
  --text-dim: #9ca3af;
  --accent: #6d5ce7;
  --accent-dim: rgba(109, 92, 231, 0.12);
  /* 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: #2563eb;
  --info-bg: rgba(37, 99, 235, 0.08);
  --warning: #d97706;
  --warning-bg: rgba(217, 119, 6, 0.08);
  --success: #16a34a;
  --success-bg: rgba(22, 163, 74, 0.08);
  --danger: #dc2626;
  --danger-bg: rgba(220, 38, 38, 0.08);
  --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;
}

/* ══════════════════════════════════════════════════════════════════
   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(--white);
  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 colour 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 COLOURS A BRAND. The active entry is marked with a
   check glyph rather than a colour swatch, because a swatch would require the
   SDK to know each brand's palette — and a brand's colours 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 colour 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;
}

/* 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 honour ──────────────────────────────
   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 colour: 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']) ui-detail-view) {
  height: 100%;
  max-height: 100%;
  overflow: hidden;
}

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

ui-module:has(ui-view:not([style*='none']) ui-detail-view) .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;
}

/* ── 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);
}

/* 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 {
  border-color: var(--accent);
  color: var(--text);
}

.uib-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-accent);
}
.uib-primary:hover {
  opacity: 0.9;
}

.uib-outline {
  background: transparent;
  border-color: var(--border);
  color: var(--text-muted);
}
.uib-outline:hover {
  border-color: var(--accent);
  color: var(--text);
}

.uib-ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);
}
.uib-ghost:hover {
  background: var(--surface-2);
  color: var(--text);
}

.uib-danger {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}
.uib-danger:hover {
  background: var(--danger-bg);
}

/* ── Disabled / Loading ── */
.uib-disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

/* ── 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 centres 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;
}


/* ══════════════════════════════════════════════════════════════════
   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 neighbour'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);
}

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-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);
}

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);
}

/* ── 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
 * colour.
 */
.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);
}

/* ─── 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 colour — 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(--white);
  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 colour —
   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) {
  display: block;
}

:is(ui-detail-hero, ui-policy-hero) .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);
  /* Containing block for the pinned status badge. */
  position: relative;
}

/* Row 1: Title + Actions */
:is(ui-detail-hero, ui-policy-hero) .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) .hero-title-group {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  min-width: 0;
  flex: 1;
}

:is(ui-detail-hero, ui-policy-hero) .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) .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 is PINNED to the hero's lower-right corner.
   Absolute rather than in flow, so it is in the same place on every record however
   many subtitles, vitals or banners sit above it — and outside .hero-body, so
   collapsing the body never hides what state the record is in. */
:is(ui-detail-hero, ui-policy-hero) .hero-status-badge-slot {
  position: absolute;
  right: var(--spacing-7);
  bottom: var(--spacing-5);
  display: inline-flex;
  flex-shrink: 0;
}
/* 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) .hero-status-badge.badge {
  flex-shrink: 0;
  font-size: var(--fs-sm);
  padding: var(--spacing-1) var(--spacing-4);
}

:is(ui-detail-hero, ui-policy-hero) .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) .hero-toggle:hover {
  color: var(--accent);
  background: var(--surface-2);
}

:is(ui-detail-hero, ui-policy-hero) .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) .hero-sidebar-toggle:hover {
  color: var(--accent);
  background: var(--surface-2);
}

:is(ui-detail-hero, ui-policy-hero) .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) .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) .hero-actions .hero-icon-btn:hover {
  background: var(--surface-2);
  color: var(--text);
}
/* Labelled action buttons.
   The old .hero-icon-btn.btn-* rules set COLOUR 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-colour. */
:is(ui-detail-hero, ui-policy-hero) .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) .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) .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) .hero-actions .hero-action-btn.btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 85%, black);
}

:is(ui-detail-hero, ui-policy-hero) .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) .hero-actions .hero-action-btn.btn-outline:hover {
  background: var(--surface-3);
}

:is(ui-detail-hero, ui-policy-hero) .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) .hero-actions .hero-action-btn.btn-danger:hover {
  background: color-mix(in srgb, var(--danger) 20%, transparent);
}

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

/* Action menu dropdown */
:is(ui-detail-hero, ui-policy-hero) .hero-action-menu {
  position: relative;
}
:is(ui-detail-hero, ui-policy-hero) .hero-action-menu .hero-menu-dropdown {
  display: none;
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: var(--spacing-2);
  min-width: 160px;
  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) .hero-action-menu.open .hero-menu-dropdown {
  display: block;
}
:is(ui-detail-hero, ui-policy-hero) .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;
}
:is(ui-detail-hero, ui-policy-hero) .hero-menu-dropdown .menu-item:hover {
  background: var(--surface-3);
}
:is(ui-detail-hero, ui-policy-hero) .hero-menu-dropdown .menu-item.danger {
  color: var(--danger);
}
:is(ui-detail-hero, ui-policy-hero) .hero-menu-dropdown .menu-separator {
  height: 1px;
  background: var(--border);
  margin: var(--spacing-2) 0;
}

/* Collapsible body */
:is(ui-detail-hero, ui-policy-hero) .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) .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) .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) .hero-subtitle-row.empty {
  display: none;
}

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

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

/* Row 3: Meta cards (compact) */
:is(ui-detail-hero, ui-policy-hero) .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) .hero-meta-row.empty {
  display: none;
}

:is(ui-detail-hero, ui-policy-hero) .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 behaviourally a button. `font: inherit` and `text-align: left` are the two
   the UA stylesheet would otherwise win: without them the label centres and drops to
   13px, which is what makes a styled button read as "not quite one of these". */
:is(ui-detail-hero, ui-policy-hero) button.hero-meta-nav {
  font: inherit;
  text-align: left;
  cursor: pointer;
}

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

:is(ui-detail-hero, ui-policy-hero) .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) .hero-meta-value {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The status card. Tinted from its own variant colour 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) .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) .hero-meta-card:is(.hero-status-card, .hero-meta-toned) .hero-meta-label,
:is(ui-detail-hero, ui-policy-hero) .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) .hero-meta-card.status-muted { color: var(--text-muted); }
:is(ui-detail-hero, ui-policy-hero) .hero-meta-card.status-info { color: var(--info); }
:is(ui-detail-hero, ui-policy-hero) .hero-meta-card.status-success { color: var(--success); }
:is(ui-detail-hero, ui-policy-hero) .hero-meta-card.status-warning { color: var(--warning); }
:is(ui-detail-hero, ui-policy-hero) .hero-meta-card.status-danger { color: var(--danger); }

:is(ui-detail-hero, ui-policy-hero) .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) .hero-meta-icon {
  display: inline-flex;
  color: var(--text-dim);
}

:is(ui-detail-hero, ui-policy-hero) .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) .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) .hero-banner-row.empty {
  display: none;
}

:is(ui-detail-hero, ui-policy-hero) .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) .hero-banner-icon {
  display: inline-flex;
  flex-shrink: 0;
}

:is(ui-detail-hero, ui-policy-hero) .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) .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) .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) .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 normalisation 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 normalisesCase) 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);
}

/* ── 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;
}

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) {
  grid-column: 1 / -1;
}

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 tokenised 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);
}

/* 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) .hero-skeleton-title {
  height: 20px;
  width: 45%;
  min-width: 160px;
}
:is(ui-detail-hero, ui-policy-hero) .hero-skeleton-badge {
  height: 22px;
  width: 64px;
  border-radius: var(--radius-md);
}
:is(ui-detail-hero, ui-policy-hero) .hero-skeleton-sub {
  height: 13px;
  border-radius: var(--radius-2xs);
}
:is(ui-detail-hero, ui-policy-hero) .hero-skeleton-sub:nth-child(1) { width: 100px; }
:is(ui-detail-hero, ui-policy-hero) .hero-skeleton-sub:nth-child(2) { width: 140px; }
:is(ui-detail-hero, ui-policy-hero) .hero-skeleton-sub:nth-child(3) { width: 80px; }
:is(ui-detail-hero, ui-policy-hero) .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) .hero-row-top,
:is(ui-detail-hero, ui-policy-hero) .hero-body {
  transition: opacity 0.25s ease;
}
:is(ui-detail-hero, ui-policy-hero)[loading] .hero-row-top,
:is(ui-detail-hero, ui-policy-hero)[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) .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) .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) .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) .hero-skeleton-subs {
  display: flex;
  gap: var(--spacing-5);
  margin-left: var(--spacing-4);
}
:is(ui-detail-hero, ui-policy-hero) .hero-skeleton-sep {
  color: var(--text-dim);
  font-size: var(--fs-sm);
}
:is(ui-detail-hero, ui-policy-hero) .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); }
ui-key-value .kv-bool-pass { color: var(--success); display: inline-flex; }
ui-key-value .kv-bool-fail { color: var(--danger); 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 colour, 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 colour — 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); }

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 colour 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 colour, not a tinted one.

   The heading carries the tone; the detail line stays --text-muted. A tone
   colour 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 colour, 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;
}

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 colour 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 flavours, because both DO something on activation and a button is
   what a keyboard and a screen reader already understand. The navigating flavour carries
   an arrow; the acting flavour 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 honours 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 neighbours 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 colour 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 grey, 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 neighbour 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)
   ══════════════════════════════════════════════════════════════════ */

/* `min-height` rather than `height`, because the negative top margin below was
   being counted TWICE: the parent sizes its content box from this element's
   natural height MINUS the -20px, and then `height: 100%` resolved against that
   already-reduced figure — surrendering the same 20px the margin had shifted it
   up by. `.att-file-area` is the only `flex: 1 1 0%` item in `.att-container`, so
   it absorbed the whole deficit and clipped its last row by exactly |margin-top|
   (measured: needed 117px, given 97px, in every state — list, grid, drop zone
   open or closed). `min-height` keeps the fill-the-panel intent when there are
   few files while letting the element grow to its content when there are many. */
ui-attachments {
  display: block;
  position: relative;
  min-height: 100%;
  /* Flush inside content panel */
  margin: -20px -24px 0;
  width: calc(100% + 48px);
}

/* 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;
}

ui-attachments .att-grid-card {
  position: relative;
}

/* ── 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(--white);
}
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-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 colour 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 COLOUR 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 neighbouring 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
     NEIGHBOURING 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 localised. */
.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 localised, 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 colour, 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. Colour 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 NORMALISER 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-coloured 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 summarising 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-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
   coloured 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;
}

/* ─── 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); }
}
.ff-ta-dropdown {
  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: 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;
}

/* 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);
}
.dt-th[aria-sort] {
  color: var(--accent);
}
.dt-sort-icon {
  display: inline-flex;
  color: var(--accent);
}
.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 colour AND a left border stripe, with `projected` also
   italic. Colour alone fails the case these exist for — greyscale printing, a
   screenshot pasted into a ticket, and the ~8% of male readers with a colour
   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);
}
/* 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;
}
.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);
}
.dt-bool-pass { color: var(--success); display: inline-flex; }
.dt-bool-fail { color: var(--danger); 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;
}

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: 0;
}

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: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

ui-inline-list .il-primary {
  font-weight: var(--fw-semibold);
}

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);
}

/* 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;
  cursor: pointer;
}

.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;
  /* Defence 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;
  cursor: pointer;
}

.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;
  cursor: pointer;
}

.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;
  cursor: pointer;
}

.cht-scatter-point:hover {
  opacity: 1 !important;
}

/* Treemap */
.cht-treemap-area {
  display: flex;
  justify-content: center;
}

.cht-treemap-rect {
  transition: opacity 0.15s;
  cursor: pointer;
}

.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 {
  cursor: pointer;
  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-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 emphasised. 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;
  /* Tokenised 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) {
  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;
}

.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;
}

/* ─── 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;
}

/* ── 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-colour 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-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);
}

ui-chat-message .chat-message-content {
  font-size: var(--fs-lg);
  line-height: var(--lh-loose);
  color: var(--text);
  word-wrap: break-word;
  overflow-wrap: break-word;
}

ui-chat-message .chat-message-content p {
  margin: 0 0 var(--spacing-3) 0;
}

ui-chat-message .chat-message-content p:last-child {
  margin-bottom: 0;
}

ui-chat-message .chat-message-content h2,
ui-chat-message .chat-message-content h3,
ui-chat-message .chat-message-content h4 {
  margin: var(--spacing-4) 0 var(--spacing-2) 0;
  font-weight: var(--fw-semibold);
}

ui-chat-message .chat-message-content h2 { font-size: var(--fs-xl); }
ui-chat-message .chat-message-content h3 { font-size: 15px; }
ui-chat-message .chat-message-content h4 { font-size: var(--fs-lg); }

ui-chat-message .chat-message-content ul,
ui-chat-message .chat-message-content ol {
  margin: var(--spacing-2) 0;
  padding-left: var(--spacing-6);
}

ui-chat-message .chat-message-content li {
  margin-bottom: var(--spacing-1);
}

ui-chat-message .chat-message-content a {
  color: var(--accent);
  text-decoration: none;
}

ui-chat-message .chat-message-content a:hover {
  text-decoration: underline;
}

ui-chat-message .chat-message-content strong {
  font-weight: var(--fw-semibold);
}

ui-chat-message .chat-message-content .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);
}

ui-chat-message .chat-message-content .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;
}

ui-chat-message .chat-message-content .chat-code-block code {
  white-space: pre;
}

/* 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; }
}

/* ── 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);
}

ui-chat-tool-card .chat-tool-args {
  font-family: var(--mono);
  font-size: var(--fs-xs);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  min-width: 0;
  opacity: 0.7;
}

ui-chat-tool-card .chat-tool-status {
  font-size: var(--fs-xs);
  color: var(--text-muted);
  flex-shrink: 0;
}

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;
}

ui-chat-tool-card .chat-tool-result {
  padding: var(--spacing-1) 0 var(--spacing-1) 18px;
  font-family: var(--mono);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--text-muted);
  white-space: pre-wrap;
  word-break: break-all;
  max-height: 80px;
  overflow-y: auto;
  opacity: 0.8;
}

ui-chat-tool-card .chat-tool-result.expanded {
  max-height: none;
}

/* ── 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);
}

ui-chat-input .chat-input-textarea {
  flex: 1;
  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(--white);
  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;
}

/* ══════════════════════════════════════════════════════════════════
   REDUCED MOTION (ND-35)
   Honours 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,
  .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;
}

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
     neighbouring 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 fulfil a request already made is a step too many. */

/* Anchors carrying .rb-btn need the box behaviour 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 colour 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;
}
/* CENTRED, not bottom-left. Bottom-left is where the pill now lives in the sidebar
   footer, and the two overlapped. Centring 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 metres 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: centred, 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 metres 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 colour 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 colour: 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 grey 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);
}
