/*!
 * appslate.css — the AppSlate design system.
 *
 * The first of the two sheets every chrome layout links; the generated
 * utilities layer (appslate-utilities.css) loads after it. Load order is not
 * what settles ties between them: everything the generator emits sits inside
 * `@layer utilities`, and everything here outside section 4 — the whole of
 * which is the one `@layer as-base` block — is unlayered, so a rule in this
 * file beats a generated utility of the same name whatever the order. A utility
 * reaches only an element no rule here claims, which is why section 7 exists at
 * all.
 *
 * Sections
 *   1. Design tokens          — the design reference's token set, `--rs-*`
 *                               renamed to `--as-*`, otherwise one-to-one.
 *   2. Extension tokens       — ours, in the reference's idiom (info, brand,
 *                               chart palette, auth vignette).
 *   3. (retired)               — was the legacy `--tw-*` bridge, which existed
 *                               so styles.css would keep resolving. It went
 *                               with that file; `--tw-*` now appears nowhere
 *                               in this repo outside historical prose. The
 *                               number is left empty rather than shifted:
 *                               sections 4 to 9 are cited by number across the
 *                               tests, the style guide and the reference map.
 *   4. Base elements & keyframes
 *   5. Chrome                 — sidebar, header, toolbar.
 *   6. Components             — buttons, inputs, cards, pills, modals, toasts.
 *   7. Utility exceptions     — rare; the generated utilities layer
 *                               (appslate-utilities.css) is the vocabulary.
 *   8. Page areas             — one sub-section per page or page family;
 *                               8.1 to 8.9 are the restyled surfaces, 8.10
 *                               to 8.18 the ones whose CSS was folded in
 *                               from an inline <style> block.
 *   9. Responsive             — the shell's shrink floor, safe areas, coarse-
 *                               pointer targets, dynamic viewport heights.
 *
 * Sections 1 and 2 are GENERATED. Edit docs/design-token-map.md and run
 * `./venv/bin/python3 scripts/build_appslate_tokens.py --write` — never change a
 * token by hand: a token that goes missing resolves to invalid and the
 * browser silently drops the one declaration that reads it. `--check` is the
 * drift guard. Sections 4 to 9 are hand-written; the generator never touches
 * them.
 *
 * New rules reference `--as-*` ONLY. `--rs-*` appears nowhere in this repo.
 */

/* ==========================================================================
   1. Design tokens
   ==========================================================================
   The reference's `themes.css`, prefix-renamed. Values are the design
   reference's own, and each block is followed by the `semi-dark` and `dark`
   overrides from the same table. Structure is load-bearing —
   the reference spells these the same way with `--rs-`, which keeps a designer
   sync mechanical.

   GENERATED from docs/design-token-map.md by scripts/build_appslate_tokens.py,
   PHASE: d3. `--check` reads that marker back, so the drift guard always
   compares against the columns this file was actually built from. Edit the
   table, regenerate, review the regression diff — do not hand-edit sections 1
   to 3. Everything from section 4 on is hand-written. */

:root,
html[data-theme-mode='light'] {
  --as-primary: #4285f4;
  --as-primary-hover: #3367d6;
  --as-primary-muted: color-mix(in srgb, #4285f4 14%, transparent);
  --as-bg: #f4f6fb;
  --as-bg-elevated: #ffffff;
  --as-bg-muted: #eef1f8;
  --as-bg-auth: linear-gradient(145deg, #e8eefc 0%, #f7f9fd 45%, #dfe8fb 100%);
  --as-sidebar: #0c0e29;
  --as-sidebar-text: #ffffff;
  --as-sidebar-muted: rgba(255, 255, 255, 0.45);
  --as-sidebar-hover: rgba(255, 255, 255, 0.08);
  --as-sidebar-active: color-mix(in srgb, #4285f4 22%, transparent);
  --as-topbar: #ffffff;
  --as-border: #e2e8f0;
  --as-border-strong: #cbd5e1;
  --as-text: #0f172a;
  --as-text-secondary: #475569;
  --as-text-muted: #94a3b8;
  --as-input-bg: #ffffff;
  --as-input-border: #d0d7e6;
  --as-danger: #dc2626;
  --as-success: #16a34a;
  --as-warning: #d97706;
  --as-shadow: 0 12px 40px rgba(15, 23, 42, 0.08);
  --as-radius: 12px;
  --as-radius-lg: 16px;
  --as-font-display: 'Manrope', system-ui, sans-serif;
  --as-font-body: 'IBM Plex Sans', system-ui, sans-serif;
  --as-scrollbar-size: 8px;
  --as-scrollbar-track: color-mix(in srgb, var(--as-bg-muted) 70%, transparent);
  --as-scrollbar-thumb: color-mix(in srgb, var(--as-border-strong) 90%, var(--as-text-muted));
  --as-scrollbar-thumb-hover: var(--as-text-muted);
}

html[data-theme-mode='semi-dark'] {
  --as-primary-muted: color-mix(in srgb, #4285f4 20%, transparent);
  --as-bg: #04071a;
  --as-bg-elevated: #0a1030;
  --as-bg-muted: #0e1638;
  --as-bg-auth: linear-gradient(155deg, #020412 0%, #081030 50%, #0c1a48 100%);
  --as-topbar: #0a1030;
  --as-border: #1a2452;
  --as-border-strong: #2a3670;
  --as-text: #e8ecf8;
  --as-text-secondary: #a8b3d4;
  --as-text-muted: #6b7699;
  --as-input-bg: #0c1438;
  --as-input-border: #243060;
  --as-shadow: 0 16px 48px rgba(0, 0, 0, 0.45);
  --as-scrollbar-track: color-mix(in srgb, var(--as-bg-muted) 80%, transparent);
  --as-scrollbar-thumb: color-mix(in srgb, var(--as-border-strong) 85%, #6b7699);
}

html[data-theme-mode='dark'] {
  --as-primary-muted: color-mix(in srgb, #4285f4 18%, transparent);
  --as-bg: #0b0d12;
  --as-bg-elevated: #141821;
  --as-bg-muted: #1a1f2b;
  --as-bg-auth: linear-gradient(155deg, #07080c 0%, #11141c 55%, #161b28 100%);
  --as-topbar: #141821;
  --as-border: #232a38;
  --as-border-strong: #343e52;
  --as-text: #f1f5f9;
  --as-text-secondary: #94a3b8;
  --as-text-muted: #64748b;
  --as-input-bg: #0f1218;
  --as-input-border: #2a3344;
  --as-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
  --as-scrollbar-track: color-mix(in srgb, var(--as-bg-muted) 80%, transparent);
  --as-scrollbar-thumb: color-mix(in srgb, var(--as-border-strong) 85%, #64748b);
}

@media print {
  html:not([data-theme-mode='light']) {
    --as-primary-muted: color-mix(in srgb, #4285f4 14%, transparent);
    --as-bg: #f4f6fb;
    --as-bg-elevated: #ffffff;
    --as-bg-muted: #eef1f8;
    --as-bg-auth: linear-gradient(145deg, #e8eefc 0%, #f7f9fd 45%, #dfe8fb 100%);
    --as-topbar: #ffffff;
    --as-border: #e2e8f0;
    --as-border-strong: #cbd5e1;
    --as-text: #0f172a;
    --as-text-secondary: #475569;
    --as-text-muted: #94a3b8;
    --as-input-bg: #ffffff;
    --as-input-border: #d0d7e6;
    --as-shadow: 0 12px 40px rgba(15, 23, 42, 0.08);
    --as-scrollbar-track: color-mix(in srgb, var(--as-bg-muted) 70%, transparent);
    --as-scrollbar-thumb: color-mix(in srgb, var(--as-border-strong) 90%, var(--as-text-muted));
  }
}

/* ==========================================================================
   2. Extension tokens
   ==========================================================================
   Gaps the reference does not cover. Every addition here is worth flagging to
   the designer so a future sync can absorb it. Kept out of section 1 so that
   block stays a verbatim port — including the theme overrides, which is why
   the extensions carry their own. */

:root,
html[data-theme-mode='light'] {
  --as-info: #8b5cf6;
  --as-info-hover: #7c3aed;
  --as-info-filled: #7c3aed;
  --as-info-filled-hover: #6d28d9;
  --as-info-muted: color-mix(in srgb, #8b5cf6 14%, transparent);
  --as-brand: #ff6f1e;
  --as-brand-hover: #f15700;
  --as-brand-muted: color-mix(in srgb, #ff6f1e 14%, transparent);
  --as-success-muted: color-mix(in srgb, #16a34a 14%, transparent);
  --as-warning-muted: color-mix(in srgb, #d97706 14%, transparent);
  --as-danger-muted: color-mix(in srgb, #dc2626 14%, transparent);
  --as-info-inverse: #ffffff;
  --as-primary-inverse: #ffffff;
  --as-danger-inverse: #ffffff;
  --as-success-hover: #15803d;
  --as-warning-hover: #b45309;
  --as-danger-hover: #b91c1c;
  --as-chart-1: #4285f4;
  --as-chart-2: #22d3ee;
  --as-chart-3: #a78bfa;
  --as-chart-4: #34d399;
  --as-chart-5: #fbbf24;
  --as-chart-6: #f472b6;
  --as-auth-vignette: radial-gradient(ellipse at center, rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.55) 100%);
  --as-shadow-soft: 0 12px 40px rgba(15, 23, 42, 0.04);
  --as-shadow-hairline: 0px 3px 4px 0px rgba(0, 0, 0, 0.02);
  --as-shadow-raised: 0px 4px 12px 0px rgba(0, 0, 0, 0.06);
  --as-tooltip-bg: #0f1014;
  --as-surface-flat: #ffffff;
  --as-primary-light: color-mix(in srgb, var(--as-primary) 7%, var(--as-bg-elevated));
  --as-danger-light: color-mix(in srgb, var(--as-danger) 7%, var(--as-bg-elevated));
  --as-success-light: color-mix(in srgb, var(--as-success) 7%, var(--as-bg-elevated));
  --as-warning-light: color-mix(in srgb, var(--as-warning) 7%, var(--as-bg-elevated));
  --as-info-light: color-mix(in srgb, var(--as-info) 7%, var(--as-bg-elevated));
  --as-table-head-bg: #fcfcfc;
  --as-header-height: 56px;
  --as-sidebar-width: 260px;
  --as-sidebar-rail: 72px;
  --as-gray-50: #f7f8fb;
  --as-gray-100: var(--as-bg-muted);
  --as-gray-200: var(--as-border);
  --as-gray-300: var(--as-border-strong);
  --as-gray-400: #afbccc;
  --as-gray-500: var(--as-text-muted);
  --as-gray-600: #64748b;
  --as-gray-700: var(--as-text-secondary);
  --as-gray-800: #1e293b;
  --as-gray-900: var(--as-text);
  --as-coal-100: #15171c;
  --as-coal-200: #13141a;
  --as-coal-300: #111217;
  --as-coal-400: #0f1014;
  --as-coal-500: #0d0e12;
  --as-coal-600: #0b0c10;
  --as-coal-black: #000000;
  --as-coal-clarity: rgba(24, 25, 31, 0.5);
}

html[data-theme-mode='semi-dark'] {
  --as-info-muted: color-mix(in srgb, #8b5cf6 20%, transparent);
  --as-brand-muted: color-mix(in srgb, #ff6f1e 20%, transparent);
  --as-success-muted: color-mix(in srgb, #16a34a 20%, transparent);
  --as-warning-muted: color-mix(in srgb, #d97706 20%, transparent);
  --as-danger-muted: color-mix(in srgb, #dc2626 20%, transparent);
  --as-shadow-soft: 0 16px 48px rgba(0, 0, 0, 0.22);
  --as-shadow-hairline: 0px 3px 4px 0px rgba(0, 0, 0, 0.03);
  --as-shadow-raised: none;
  --as-tooltip-bg: #04071a;
  --as-surface-flat: #0e1638;
  --as-table-head-bg: #0e1638;
  --as-gray-50: #0c1334;
  --as-gray-400: #4a5684;
  --as-gray-600: #8994b6;
  --as-gray-800: #c8cfe6;
}

html[data-theme-mode='dark'] {
  --as-info-muted: color-mix(in srgb, #8b5cf6 18%, transparent);
  --as-brand-muted: color-mix(in srgb, #ff6f1e 18%, transparent);
  --as-success-muted: color-mix(in srgb, #16a34a 18%, transparent);
  --as-warning-muted: color-mix(in srgb, #d97706 18%, transparent);
  --as-danger-muted: color-mix(in srgb, #dc2626 18%, transparent);
  --as-shadow-soft: 0 16px 48px rgba(0, 0, 0, 0.25);
  --as-shadow-hairline: 0px 3px 4px 0px rgba(0, 0, 0, 0.03);
  --as-shadow-raised: none;
  --as-tooltip-bg: #0b0c10;
  --as-surface-flat: #1f212a;
  --as-table-head-bg: #13141a;
  --as-gray-50: #171c26;
  --as-gray-400: #4c596e;
  --as-gray-600: #7c8ba1;
  --as-gray-800: #cbd5e1;
}

@media print {
  html:not([data-theme-mode='light']) {
    --as-info-muted: color-mix(in srgb, #8b5cf6 14%, transparent);
    --as-brand-muted: color-mix(in srgb, #ff6f1e 14%, transparent);
    --as-success-muted: color-mix(in srgb, #16a34a 14%, transparent);
    --as-warning-muted: color-mix(in srgb, #d97706 14%, transparent);
    --as-danger-muted: color-mix(in srgb, #dc2626 14%, transparent);
    --as-shadow-soft: 0 12px 40px rgba(15, 23, 42, 0.04);
    --as-shadow-hairline: 0px 3px 4px 0px rgba(0, 0, 0, 0.02);
    --as-shadow-raised: 0px 4px 12px 0px rgba(0, 0, 0, 0.06);
    --as-tooltip-bg: #0f1014;
    --as-surface-flat: #ffffff;
    --as-table-head-bg: #fcfcfc;
    --as-gray-50: #f7f8fb;
    --as-gray-400: #afbccc;
    --as-gray-600: #64748b;
    --as-gray-800: #1e293b;
  }
}

/* ==========================================================================
   4. Base elements & keyframes
   ==========================================================================
   Two kinds of base, in two cascade positions. 4.1 is the framework Preflight
   ported out of `styles.css`, held inside the `as-base` layer so it stays the
   floor every component and utility overrides. 4.2 on is the design
   reference's own `index.css`, unlayered like the rest of this file.
   `.as-scroll` is still inert — nothing in the app carries that name yet. The
   focus ring is live as of the palette phase, the type since the typography
   phase, and the entrance animation since the chrome phase. */

/* --------------------------------------------------------------------------
   4.1 Preflight — the framework reset
   --------------------------------------------------------------------------
   Two thirds of the 88 element-, attribute- and pseudo-element-only rules that
   were the last thing `styles.css` owned outright (`docs/styles-css-residual.md`
   §6 step 2): Tailwind v3's Preflight, the popover arrow family Flowbite ships
   beside it, and the four late base rules the app added below them (the `ul`/
   `ol` list marks and gutter, `kbd`). The native-control half is 4.2, and is
   unlayered for the reason recorded there. The frozen sheet held two Preflight
   copies from two generations of the Metronic build; only the later one was
   reachable, and that is the one here — nine rules were dropped rather than
   ported because a byte-equivalent or wider copy already won the cascade.

   THE LAYER IS THE POINT. Preflight has to lose every tie: it is the floor a
   component or a utility overrides, and in `styles.css` it got that by being
   the first thing in the first stylesheet. Re-declared here it would be the
   LAST thing instead, and every zero- and one-specificity tie would flip. A
   cascade layer restores the floor exactly — an unlayered rule beats a layered
   one whatever its specificity — and `as-base` is the first layer this document
   names, so the generated utilities (`@layer utilities`, loaded after) outrank
   it too. That is the endgame relationship, and it closes a live defect on the
   way: while this reset was unlayered, `*, ::before, ::after`'s `border-color`
   beat every generated-layer `border-*` colour utility that `styles.css` did
   not happen to define a second time — 42 of them painted the reset's gray
   instead of the colour the markup asked for — and `a`'s `color: inherit` did
   the same to every generated-only text colour on a link, which is why
   `text-as-primary` links had been rendering as body copy.

   Do NOT move these rules out of the layer, and do not add an earlier `@layer`
   block to this file. `AppslateResidualTest` pins both, plus the emptiness of
   the bucket this came from.

   Colour: every value is the one that was rendering. The reset's
   `var(--tw-gray-200)` and the placeholder's `var(--tw-gray-400)` became the
   `--as-*` twins the bridge already resolved them to, and `kbd`'s `#cbd5e1`
   became `--as-gray-300`, the token whose light value it is. The popover
   arrow's `#e5e7eb` stays a literal — see the note on that family. */
@layer as-base {
  *,
  ::before,
  ::after {
    box-sizing: border-box;
    border-width: 0;
    border-style: solid;
    border-color: var(--as-gray-200);
  }

  /* The family here is inert in practice — `body` below sets `--as-font-body`
     and everything that renders is inside it — but it is what an element
     outside the body flow inherits, so it moves with the rest. */
  html,
  :host {
    line-height: 1.5;
    -webkit-text-size-adjust: 100%;
    -moz-tab-size: 4;
    -o-tab-size: 4;
    tab-size: 4;
    font-family: Inter, system-ui, sans-serif;
    font-feature-settings: normal;
    font-variation-settings: normal;
    -webkit-tap-highlight-color: transparent;
  }

  body {
    margin: 0;
  }

  hr {
    height: 0;
    color: inherit;
    border-top-width: 1px;
  }

  abbr:where([title]) {
    -webkit-text-decoration: underline dotted;
    text-decoration: underline dotted;
  }

  a {
    color: inherit;
    text-decoration: inherit;
  }

  b,
  strong {
    font-weight: bolder;
  }

  /* Preflight's mono normalization. The frozen sheet carried `.font-mono` on
     the same selector list — the generated layer serves that class now, with
     the identical stack — so only the element half moves. `font-size: 1em`
     undoes the UA's smaller monospace default; `kbd` below re-sets it, and has
     to stay after this rule to keep doing so.

     Leaving the class behind also un-stuck a size: `1em` used to ride along
     with `.font-mono` and outrank the `text-*` beside it (37 markup sites pair
     the two), so `class="font-mono text-xs"` rendered at the inherited size.
     The generated class sets the family alone, which is all it ever meant. */
  code,
  kbd,
  samp,
  pre {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
      'Liberation Mono', 'Courier New', monospace;
    font-feature-settings: normal;
    font-variation-settings: normal;
    font-size: 1em;
  }

  small {
    font-size: 80%;
  }

  sub,
  sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }

  sub {
    bottom: -0.25em;
  }

  sup {
    top: -0.5em;
  }

  table {
    text-indent: 0;
    border-color: inherit;
    border-collapse: collapse;
  }

  button,
  input,
  optgroup,
  select,
  textarea {
    font-family: inherit;
    font-feature-settings: inherit;
    font-variation-settings: inherit;
    font-size: 100%;
    font-weight: inherit;
    line-height: inherit;
    letter-spacing: inherit;
    color: inherit;
    margin: 0;
    padding: 0;
  }

  button,
  select {
    text-transform: none;
  }

  /* The frozen sheet also carried the later build's narrower
     `input:where([type="button"])` spelling of this rule with identical
     declarations; this one covers every element it did. */
  button,
  [type='button'],
  [type='reset'],
  [type='submit'] {
    -webkit-appearance: button;
    background-color: transparent;
    background-image: none;
  }

  :-moz-focusring {
    outline: auto;
  }

  :-moz-ui-invalid {
    box-shadow: none;
  }

  progress {
    vertical-align: baseline;
  }

  ::-webkit-inner-spin-button,
  ::-webkit-outer-spin-button {
    height: auto;
  }

  [type='search'] {
    -webkit-appearance: textfield;
    outline-offset: -2px;
  }

  ::-webkit-search-decoration {
    -webkit-appearance: none;
  }

  ::-webkit-file-upload-button {
    -webkit-appearance: button;
    font: inherit;
  }

  summary {
    display: list-item;
  }

  blockquote,
  dl,
  dd,
  h1,
  h2,
  h3,
  h4,
  h5,
  h6,
  hr,
  figure,
  p,
  pre {
    margin: 0;
  }

  fieldset {
    margin: 0;
    padding: 0;
  }

  legend {
    padding: 0;
  }

  ol,
  ul,
  menu {
    margin: 0;
    padding: 0;
  }

  dialog {
    padding: 0;
  }

  textarea {
    resize: vertical;
  }

  input::-moz-placeholder,
  textarea::-moz-placeholder {
    opacity: 1;
    color: var(--as-gray-400);
  }

  input::placeholder,
  textarea::placeholder {
    opacity: 1;
    color: var(--as-gray-400);
  }

  button,
  [role="button"] {
    cursor: pointer;
  }

  :disabled {
    cursor: default;
  }

  img,
  svg,
  video,
  canvas,
  audio,
  iframe,
  embed,
  object {
    display: block;
    vertical-align: middle;
  }

  img,
  video {
    max-width: 100%;
    height: auto;
  }

  [hidden] {
    display: none;
  }

  /* --- The popover arrow (Flowbite) --------------------------------------
     Eighteen unreachable rules, kept on the audit's bias rule ("when in doubt,
     keep") and on one unmet condition.

     No first-party markup writes `data-popper-arrow`, `data-popover` or
     `role="tooltip"`. The attribute appears twice in the repo and both are
     popper.js's own default arrow selector, inside a bundle that never creates
     the element: `core.bundle.js` and `datatables/allowed-ip-addresses.js`
     (re-verified 2026-08-09).

     The `.tooltip-arrow` half this block used to be paired with is already gone
     — it died with the frozen stylesheet. What is left is the Metronic bundle's
     disposition: `core.bundle.js` still ships, and the strip is its to make.
     `MIGRATION_PLAN_JS.md` P2/P3 own that; strip this block in the same change.

     `#e5e7eb` is the vendor's own border and stays a literal for the same
     reason: nothing renders it, so no token claim about it could be checked. */
  [data-popper-arrow],
  [data-popper-arrow]:before {
    position: absolute;
    width: 8px;
    height: 8px;
    background: inherit;
  }

  [data-popper-arrow] {
    visibility: hidden;
  }

  [data-popper-arrow]:before {
    content: "";
    visibility: visible;
    transform: rotate(45deg);
  }

  [data-popper-arrow]:after {
    content: "";
    visibility: visible;
    transform: rotate(45deg);
    position: absolute;
    width: 9px;
    height: 9px;
    background: inherit;
  }

  [role="tooltip"] > [data-popper-arrow]:before {
    border-style: solid;
    border-color: #e5e7eb;
  }

  [role="tooltip"] > [data-popper-arrow]:after {
    border-style: solid;
    border-color: #e5e7eb;
  }

  [data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow]:before {
    border-bottom-width: 1px;
    border-right-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow]:after {
    border-bottom-width: 1px;
    border-right-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow]:before {
    border-bottom-width: 1px;
    border-left-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow]:after {
    border-bottom-width: 1px;
    border-left-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow]:before {
    border-top-width: 1px;
    border-left-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow]:after {
    border-top-width: 1px;
    border-left-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow]:before {
    border-top-width: 1px;
    border-right-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow]:after {
    border-top-width: 1px;
    border-right-width: 1px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='top'] > [data-popper-arrow] {
    bottom: -5px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='bottom'] > [data-popper-arrow] {
    top: -5px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='left'] > [data-popper-arrow] {
    right: -5px;
  }

  [data-popover][role="tooltip"][data-popper-placement^='right'] > [data-popper-arrow] {
    left: -5px;
  }

  /* --- The native select's chevron ---------------------------------------
     Framework base, and layered even though it styles a native control. It was
     the first rule of 4.2 to move in, at a time when the rest of that section
     was held unlayered by the Play-CDN Preflight — the properties this one sets
     are `background-*`, which that Preflight never touched, so it could move
     alone. C-6 retired the CDN and 4.2 followed it in, so section 4 is now
     layered whole. Being unlayered is in fact what
     was WRONG with it — `select:not([size])` is one step more specific than
     `.select`, so this rule had been outranking the design's own chevron in
     section 6 ever since that component landed, and every classed select in the
     app drew Metronic's arrow at Metronic's size and offset. Inside the layer
     `.select` and `.select-sm` win their own artwork, and a bare `<select>`
     (which also keeps the browser's native arrow, since nothing sets
     `appearance` on it) still gets this one. */
  select:not([size]) {
    background-image: url("data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0ndHJ1ZScgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBmaWxsPSdub25lJyB2aWV3Qm94PScwIDAgMTAgNic+IDxwYXRoIHN0cm9rZT0nIzZCNzI4MCcgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJyBzdHJva2Utd2lkdGg9JzInIGQ9J20xIDEgNCA0IDQtNCcvPiA8L3N2Zz4=");
    background-position: right 0.75rem center;
    background-repeat: no-repeat;
    background-size: 0.75em 0.75em;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  :is([dir=rtl]) select:not([size]) {
    background-position: left 0.75rem center;
    padding-right: 0.75rem;
    padding-left: 0;
  }

  /* --- The app's own base rules -------------------------------------------
     Four rules the app added below Preflight in the frozen sheet. They keep
     their order relative to it: the list marks and gutter have to land after
     `ol, ul, menu`'s zeroing, or the reset takes them back. */
  ul,
  ol {
    padding-left: 1rem;
    margin-left: 0.25rem;
  }

  ul {
    list-style-type: disc;
  }

  ol {
    list-style-type: decimal;
  }

  kbd {
    display: inline-block;
    padding: 0.25rem 0.5rem;
    font-size: 0.75rem;
    line-height: 1;
    border-radius: 0.25rem;
    border: 1px solid var(--as-gray-300);
  }

  /* --------------------------------------------------------------------------
     4.3 The document
     -------------------------------------------------------------------------- */

  /* The document's type, and the only place it is set.

     `styles.css` puts the family on `html` (Inter, twice, from two generations of
     the Metronic build); this sets it on `body`, which is more specific for
     everything that renders and leaves the legacy rule reachable by nothing. The
     size is the reference's own 14px, written in `rem` so a reader who raises
     their browser's default still gets a proportional page — the two are equal
     at the default 16px root, which nothing in this app changes.

     The four chrome layouts used to pin this with a `text-base` utility on
     `<body>` (16px, class specificity), which would have silently outranked this
     rule and left the phase looking done. The utility is gone from the markup;
     putting it back would reinstate the old size everywhere at once. */
  body {
      font-family: var(--as-font-body);
      font-size: 0.875rem;
      line-height: 1.5;
      /* The last declaration of the reference's `body` rule to land. The chrome
         layouts painted body text with a `text-gray-700` utility — `#4b5675`,
         a colour the reference gives to SECONDARY text — so every page rendered
         its primary copy one step too pale. The utility is gone from the four
         layouts that carried it; putting it back would outrank this rule by
         specificity and restore the old colour everywhere at once. */
      color: var(--as-text);
      /* macOS-only, and the reference's own default. Four of the five layouts
         already carried the equivalent utility on `<body>`; this is what gives
         the fifth (the support docs) the same rendering. */
      -webkit-font-smoothing: antialiased;
  }

  /* Headings are the display face. Metronic's reset zeroes `font-size` and
     `font-weight` on h1-h6 (both `inherit`), so every heading in the app is
     sized by a utility class and nothing here changes that — this sets only the
     family and the tightening the reference pairs with it. h5/h6 are deliberately
     absent: the reference stops at h4, and the two smaller levels are used in
     this app for label-weight text that reads better in the body face. */
  h1,
  h2,
  h3,
  h4 {
      font-family: var(--as-font-display);
      letter-spacing: -0.02em;
  }

  /* One visible focus ring for the whole app, per the reference. Legacy CSS opts
     out where it means to (`.focus:outline-none`, `.agent-tab-title:focus`, the
     slide-deck ring) — those selectors are more specific and keep winning, which
     is the intent: this is the default, not an override. */
  :focus-visible {
    outline: 2px solid var(--as-primary);
    outline-offset: 2px;
  }

  /* --------------------------------------------------------------------------
     4.2 Native form controls, the document's own base, and the keyframes
     --------------------------------------------------------------------------
     The Flowbite forms base, the app's date-input direction fix beside it, the
     entrance/exit keyframes and the themed scrollbar utility.

     This block spent one release OUTSIDE the layer, and the reason is worth
     keeping: three pages loaded the Tailwind v3 Play CDN, which injects its own
     Preflight as an unlayered `<style>` and so outranks anything in a layer
     whatever the specificity. For 4.1 that cost nothing — the injected copy IS
     Preflight and sets the same properties to the same values — but it carries
     no forms plugin, so a layered `[type='checkbox']` lost its `border-width`
     and its `color` (the checked fill, painted through `currentColor`) on
     exactly those pages; 16 datasource checkboxes on the network view measured
     it. The two `intelmaster` layouts dropped the script, the network view was
     deleted with the rest of its page, and this block came back into the layer
     where the Tailwind-native arrangement puts it — which is also what lets a
     `text-*` utility recolour a checkbox tick again.

     Section 6's `.checkbox`/`.radio`/`.switch` are two-class-or-better
     selectors and keep winning over these, as they always did. The cascade that
     DID move when the frozen sheet went is against single-class utilities: a
     `rounded` or `h-5` on a bare control used to beat these rules by coming
     later and now loses, which is why the three declarations that existed only
     to win such a tie were dropped rather than ported (each is annotated where
     it would have been). One rule had to leave this section for the same reason
     in reverse — see the select chevron in 4.1.

     This is the unclassed spelling of every control section 6 restyles under a
     class: 76 of the app's 135 checkbox tags carry no `.checkbox`, which is why
     the two now agree on `--as-primary` and `--as-input-border` rather than
     Flowbite's `#1C64F2` and `#6B7280`. */

  ::-webkit-datetime-edit-fields-wrapper {
    padding: 0;
  }

  input[type="time"]::-webkit-calendar-picker-indicator {
    background: none;
  }

  [multiple] {
    background-image: initial;
    background-position: initial;
    background-repeat: unset;
    background-size: initial;
    padding-right: 0.75rem;
    -webkit-print-color-adjust: unset;
    print-color-adjust: unset;
  }

  /* `color` is the checked fill: the `:checked` rule below paints
     `currentColor`. The frozen rule also seeded `--tw-shadow: 0 0 #0000` for
     the focus ring's `box-shadow` chain, which is resolved inline below, so
     the last Tailwind-internal read on this control is gone. */
  [type='checkbox'],
  [type='radio'] {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    padding: 0;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
    display: inline-block;
    vertical-align: middle;
    background-origin: border-box;
    -webkit-user-select: none;
    -moz-user-select: none;
    user-select: none;
    flex-shrink: 0;
    height: 1rem;
    width: 1rem;
    color: var(--as-primary);
    background-color: var(--as-input-bg);
    border-color: var(--as-input-border);
    border-width: 1px;
  }

  /* `[type='checkbox'] { border-radius: 0px }` is not carried over: with
     `appearance: none` the computed radius is already 0, so the declaration only
     ever acted as a specificity tie against a `rounded` utility — a tie it lost
     in the frozen sheet (eight files write `rounded` on a bare checkbox: seven
     views and `dataslate.js`) and would have won here, squaring all of them off. The radio's radius is real
     and stays; no markup pairs `rounded` with a radio. */
  [type='radio'] {
    border-radius: 100%;
  }

  /* The legacy ring, resolved: a 2px halo in the page surface and a 4px ring
     outside it. `#fff` was the halo and is `--as-bg-elevated`, which is the
     same colour in light and the right one on the two dark themes. */
  [type='checkbox']:focus,
  [type='radio']:focus {
    outline: 2px solid transparent;
    outline-offset: 2px;
    box-shadow: 0 0 0 2px var(--as-bg-elevated), 0 0 0 4px var(--as-primary);
  }

  /* The `.dark`-scoped duplicates of this rule and of `[type='radio']:checked`
     were dropped with the move: their declarations were identical to the base
     pair, so the two pages that put `.dark` on a container at the time rendered
     exactly what they had. Both are retired now and nothing sets the class. */
  [type='checkbox']:checked,
  [type='radio']:checked {
    border-color: transparent;
    background-color: currentColor;
    background-size: 0.55em 0.55em;
    background-position: center;
    background-repeat: no-repeat;
  }

  [type='checkbox']:checked {
    background-image: url("data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0ndHJ1ZScgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBmaWxsPSdub25lJyB2aWV3Qm94PScwIDAgMTYgMTInPiA8cGF0aCBzdHJva2U9J3doaXRlJyBzdHJva2UtbGluZWNhcD0ncm91bmQnIHN0cm9rZS1saW5lam9pbj0ncm91bmQnIHN0cm9rZS13aWR0aD0nMycgZD0nTTEgNS45MTcgNS43MjQgMTAuNSAxNSAxLjUnLz4gPC9zdmc+");
    background-repeat: no-repeat;
    background-size: 0.55em 0.55em;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  [type='radio']:checked {
    background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PScwIDAgMTYgMTYnIGZpbGw9J3doaXRlJyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnPjxjaXJjbGUgY3g9JzgnIGN5PSc4JyByPSczJy8+PC9zdmc+");
    background-size: 1em 1em;
  }

  [type='checkbox']:indeterminate {
    background-image: url("data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0ndHJ1ZScgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBmaWxsPSdub25lJyB2aWV3Qm94PScwIDAgMTYgMTInPiA8cGF0aCBzdHJva2U9J3doaXRlJyBzdHJva2UtbGluZWNhcD0ncm91bmQnIHN0cm9rZS1saW5lam9pbj0ncm91bmQnIHN0cm9rZS13aWR0aD0nMycgZD0nTTAuNSA2aDE0Jy8+IDwvc3ZnPg==");
    background-color: currentColor;
    border-color: transparent;
    background-position: center;
    background-repeat: no-repeat;
    background-size: 0.55em 0.55em;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  [type='checkbox']:indeterminate:hover,
  [type='checkbox']:indeterminate:focus {
    border-color: transparent;
    background-color: currentColor;
  }

  /* `font-size: unset` is not carried over. Against Preflight's own
     `input { font-size: 100% }` it was a no-op — both resolve to the parent's
     size — and its only real effect was to tie with a class at equal
     specificity. In the frozen sheet it lost that tie (`.image-input`, the 11px
     file field `UI::form_html` emits, is declared 10,000 lines further down);
     moved here it would have won it and grown every image field's control text
     to 14px. */
  [type='file'] {
    background: unset;
    border-color: inherit;
    border-width: 0;
    border-radius: 0;
    padding: 0;
    line-height: inherit;
  }

  [type='file']:focus {
    outline: 1px auto inherit;
  }

  /* Metronic dressed the native file button as a small `.btn`; it now takes
     `.btn-light`'s surface so it re-themes with the rest of the neutral
     buttons. Its two theme-constant reads (`--tw-light`, `--tw-light-active`)
     and the hover shadow (`--tw-default-box-shadow`, already `none` on both
     dark themes) had no equal `--as-*` twin, so this is the one place in the
     port where a value moved on purpose. */
  input[type=file]::file-selector-button {
    align-items: center;
    cursor: pointer;
    line-height: 1;
    border-radius: 0.375rem;
    border: 1px solid transparent;
    outline: none;
    color: var(--as-gray-700);
    border-color: var(--as-gray-300);
    background-color: var(--as-bg-elevated);
    height: 1.75rem;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    margin-left: 0;
    margin-right: 0.5rem;
    font-weight: 500;
    font-size: 0.6875rem;
    gap: 0rem;
  }

  input[type=file]::file-selector-button:hover {
    border-color: var(--as-gray-300);
    background-color: var(--as-bg-muted);
    box-shadow: none;
    color: var(--as-gray-800);
  }

  :is([dir=rtl]) input[type=file]::file-selector-button {
    padding-right: 2rem;
    padding-left: 1rem;
  }

  input[type="range"]::-webkit-slider-thumb {
    height: 1.25rem;
    width: 1.25rem;
    background: var(--as-primary);
    border-radius: 9999px;
    border: 0;
    appearance: none;
    -moz-appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
  }

  input[type="range"]:disabled::-webkit-slider-thumb {
    background: var(--as-text-muted);
  }

  /* The frozen rule's ring chain was invalid at computed-value time — it wrote
     the ring alpha as `1px` — so this control has never drawn a focus shadow.
     Carrying the chain forward would carry the defect; the transparent outline
     it also set is what actually rendered and is what is kept. */
  input[type="range"]:focus::-webkit-slider-thumb {
    outline: 2px solid transparent;
    outline-offset: 2px;
  }

  input[type="range"]::-moz-range-thumb {
    height: 1.25rem;
    width: 1.25rem;
    background: var(--as-primary);
    border-radius: 9999px;
    border: 0;
    appearance: none;
    -moz-appearance: none;
    -webkit-appearance: none;
    cursor: pointer;
  }

  input[type="range"]:disabled::-moz-range-thumb {
    background: var(--as-text-muted);
  }

  input[type="range"]::-moz-range-progress {
    background: var(--as-primary);
  }

  input[type="range"]::-ms-fill-lower {
    background: var(--as-primary);
  }

  /* `direction: rtl` puts the native date picker's indicator on the left; the
     `::-webkit-datetime-edit` child is flipped back so the digits read LTR. */
  input[type="date"],
  input[type="datetime-local"] {
    direction: rtl;
    text-align: left;
  }

  input[type="date"]::-webkit-calendar-picker-indicator,
  input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    cursor: pointer;
  }

  input[type="date"]::-webkit-datetime-edit,
  input[type="datetime-local"]::-webkit-datetime-edit {
    direction: ltr;
    text-align: left;
  }

  :is([dir=rtl]) input[type="date"],
  :is([dir=rtl]) input[type="datetime-local"] {
    direction: ltr;
  }

  @keyframes as-enter {
    from {
      opacity: 0;
      transform: scale(0.96) translateY(-4px);
    }

    to {
      opacity: 1;
      transform: scale(1) translateY(0);
    }
  }

  @keyframes as-exit {
    from {
      opacity: 1;
      transform: scale(1) translateY(0);
    }

    to {
      opacity: 0;
      transform: scale(0.96) translateY(-4px);
    }
  }

  /* The page-entrance motion. `as-enter`/`as-exit` above are the reference's
     own keyframes, and they are the *popover* gesture — a menu scaling in from
     above its trigger. This is the other one: the fade-up its page header and
     metric tiles animate with, which the reference expresses as inline motion
     props rather than CSS.

     `backwards` and not `both` on purpose. With `both` the element keeps the
     final keyframe's `transform: translateY(0)` forever, and a transform — even
     an identity one — makes the element a containing block for every fixed
     descendant, which would quietly re-anchor any popover rendered inside it.
     `backwards` applies the opening frame during the delay and then hands the
     element back to its own styles, which is exactly where the animation ends. */
  @keyframes as-rise {
    from {
      opacity: 0;
      transform: translateY(8px);
    }

    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

  /* One class and one custom property, so a staggered group (dashboard tiles)
     sets `--as-rise-delay` per item and needs no second rule. */
  .as-rise {
    animation: as-rise 280ms ease-out backwards;
    animation-delay: var(--as-rise-delay, 0ms);
  }

  @media (prefers-reduced-motion: reduce) {
    .as-rise {
      animation: none;
    }
  }

  /* Themed scrollbars — the token values follow the active theme. */
  .as-scroll {
    scrollbar-width: thin;
    scrollbar-color: var(--as-scrollbar-thumb) var(--as-scrollbar-track);
  }

  .as-scroll::-webkit-scrollbar {
    width: var(--as-scrollbar-size);
    height: var(--as-scrollbar-size);
  }

  .as-scroll::-webkit-scrollbar-track {
    background: var(--as-scrollbar-track);
    border-radius: 999px;
  }

  .as-scroll::-webkit-scrollbar-thumb {
    background-color: var(--as-scrollbar-thumb);
    border-radius: 999px;
    border: 2px solid transparent;
    background-clip: padding-box;
  }

  .as-scroll::-webkit-scrollbar-thumb:hover {
    background-color: var(--as-scrollbar-thumb-hover);
    border: 2px solid transparent;
    background-clip: padding-box;
  }

  .as-scroll::-webkit-scrollbar-corner {
    background: transparent;
  }
}

/* ==========================================================================
   5. Chrome
   ========================================================================== */

/* --------------------------------------------------------------------------
   5.1 Chrome geometry
   --------------------------------------------------------------------------
   `--as-sidebar-width` and `--as-sidebar-rail` are the two constants;
   `--as-sidebar-current` is the one that says which is in effect. It lives on
   `body` because that is where `.sidebar-collapse` lands, and because the
   `.demo1` layout rules below read it — the wrapper's left padding and the
   fixed header's left edge both follow the rail without a second source of
   truth.

   The rail applies from `lg` up only. Below it the sidebar is an off-canvas
   drawer that is either open at full width or not on screen at all, so
   collapsing it there would only make the drawer narrow.

   `--as-content-height` is the vertical budget a full-height page has under
   the header, toolbar and standard padding — what `.panel-container`, the
   agent chat layout and the flow editor lock to so they end at the bottom of
   the window instead of growing past it. The 150px is the chrome measured
   against `objectdetails_listfields.blade.php` and `pattern.blade.php`.
   It is on `body` rather than `.demo1` so a layout that is not a `.demo1`
   body can read it too. */
body {
  --as-sidebar-current: var(--as-sidebar-width);
  --as-content-height: calc(100vh - var(--as-header-height) - 150px);
}

@media (min-width: 1024px) {
  body.sidebar-collapse {
    --as-sidebar-current: var(--as-sidebar-rail);
  }
}

/* The draft-mode and draft-error strips are `position: fixed` at the top of
   the window, so the page under them has to start 40px lower. `layouts/app`
   sets the class when either strip renders; it used to emit a one-rule
   `<style>` block inside each of the two @ifs. */
body.has-fixed-banner {
  padding-top: 40px;
}

/* The island's own nav bar — `resources/views/navigation-menu.blade.php`,
   rendered by `layouts/app` and nothing else. It is chrome, not document, and
   the layouts that link `print.css` drop their bar and rail through it while
   section 8.18 drops the compliance header and footer. This one links no print
   sheet, so its rule lives here. */
@media print {
  .app-nav {
    display: none;
  }
}

/* The shell layout itself, ported out of `styles.css` (pass (c) step 3). Its
   `.demo1 { … }` block came too, and then vanished: every declaration in it
   was a bridge hop from an `--as-*` token to a `--tw-*` name these four rules
   then read back, so they read the tokens directly and the block is gone.
   `--tw-sidebar-defualt-width` went with it — nothing ever read it — and the
   200ms/ease-out pair is inlined for the same reason. The three markup
   classes are unchanged: `.demo1` on the body, `.sidebar-fixed` and
   `.header-fixed` beside it. */
.demo1.header-fixed .wrapper {
  padding-top: var(--as-header-height);
}

@media (min-width: 1024px) {
  .demo1.sidebar-fixed .wrapper {
    padding-left: var(--as-sidebar-current);
    transition: padding-left 200ms ease-out;
  }

  .demo1.sidebar-fixed.header-fixed .header {
    left: var(--as-sidebar-current);
    transition: left 200ms ease-out;
  }

  .demo1.sidebar-fixed.header-fixed .wrapper {
    padding-top: var(--as-header-height);
  }
}

/* --------------------------------------------------------------------------
   5.2 Top bar
   -------------------------------------------------------------------------- */

/* It carried `bg-white border-b border-gray-200` in the markup, and a utility
   whose value is a literal cannot follow a theme — so the one element present
   on every page stayed a white strip above a dark one. `--as-topbar` IS
   `#ffffff` in the light theme; the border is the design's own `--as-border`.

   The height is declared here rather than on `.demo1 .header` so the support
   docs get it too: that layout is not a `.demo1` body, so its bar was sized by
   its contents (41px) while its shell reserved the 70px fallback above it. */
.header-appslate {
  height: var(--as-header-height);
  background-color: var(--as-topbar);
  border-bottom: 1px solid var(--as-border);
  /* The bar is `fixed … left-0 right-0`, so on a device that reports a
     horizontal inset its content would sit under the notch (§9). Below `lg`
     that is both edges; at `lg` and up the rail already holds the left one off,
     and no device that reports a left inset is that wide anyway. */
  padding-right: env(safe-area-inset-right);
  padding-left: env(safe-area-inset-left);
}

/* The bar's left group takes the slack, so the search field inside it has room
   to grow into. Without this the container's `justify-between` hands every
   spare pixel to the gap between the two groups and the field stays at its
   minimum. */
#header_start {
  flex: 1 1 auto;
  min-width: 0;
}

/* The bar's ghost icon buttons — search (below `md`), apps, notifications, the
   theme control. One class rather than the utility cluster each carried,
   because two of the four utilities in it were doing nothing:
   `dropdown-open:bg-gray-100` has no rule anywhere (only the `-gray-200` and
   `-primary-light` spellings were generated), so the open state was half
   applied. The open state below is keyed off the attribute the dropdown
   component actually toggles. */
.topbar-action,
.topbar-search {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 0;
  border-radius: var(--as-radius);
  background-color: transparent;
  color: var(--as-text-secondary);
  font-size: 1.125rem;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.topbar-action:hover,
.topbar-search:hover,
[data-dropdown].open > .topbar-action {
  background-color: var(--as-bg-muted);
  color: var(--as-text);
}

/* The two controls that only exist while the sidebar is a drawer: the bar's
   hamburger and the drawer's own dismiss.

   Their visibility is a rule here rather than an `lg:hidden` utility on the
   markup, and that is not a style preference. The generated utilities ship
   inside `@layer utilities`, and an unlayered declaration beats every layered
   one no matter what order the files load in — so any element this file gives
   a `display` to can no longer be hidden by a utility. Chrome responsiveness
   therefore lives here, next to the rule it has to beat. */
.drawer-only {
  display: inline-flex;
}

@media (min-width: 1024px) {
  .drawer-only {
    display: none;
  }
}

/* The search trigger. It opens the existing search modal — the design's inline
   typeahead is a feature, not a restyle — so this is a button wearing the
   design's search field: full field from `md` up, and (through the shared rule
   above) the same 36px ghost square as its neighbours below that, where a 24rem
   field cannot fit. */
.topbar-search {
  gap: 0.5rem;
}

.topbar-search-label {
  display: none;
}

@media (min-width: 768px) {
  .topbar-search {
    /* Takes the space left over rather than claiming a share of it: the
       reference's `w-full max-w-sm` has the row to itself, and here the trail
       is beside it — asking for 24rem first drove the breadcrumb to a few
       characters at `md`. A zero basis makes leftover space the whole claim. */
    flex: 1 1 0;
    width: auto;
    min-width: 6rem;
    max-width: 24rem;
    justify-content: flex-start;
    padding: 0 0.75rem;
    border: 1px solid var(--as-border);
    background-color: var(--as-input-bg);
    color: var(--as-text-muted);
    font-size: 1rem;
  }

  .topbar-search:hover {
    border-color: var(--as-border-strong);
    background-color: var(--as-input-bg);
    color: var(--as-text-muted);
  }

  .topbar-search-label {
    display: inline;
    font-size: 0.875rem;
  }
}

/* The header's theme control renders all three glyphs and shows the one for
   the theme in effect. Doing it in CSS, off the same attribute the tokens key
   off, is what keeps the icon from being a frame behind the colours it
   describes: the pre-paint reader sets `data-theme-mode` before anything is
   painted, whereas a script that rewrites the `<use href>` cannot run until
   the document is parsed.

   Both selectors carry the ancestor attribute so they outrank
   `svg.icon:not(.hidden) { display: inline-block }` in styles.css outright,
   rather than tying with it and depending on load order. */
html[data-theme-mode] svg.icon[data-as-theme-icon] {
  display: none;
}

html[data-theme-mode='light'] svg.icon[data-as-theme-icon='light'],
html[data-theme-mode='semi-dark'] svg.icon[data-as-theme-icon='semi-dark'],
html[data-theme-mode='dark'] svg.icon[data-as-theme-icon='dark'] {
  display: inline-block;
}

/* Breadcrumb, moved out of styles.css. Two of the four rules it had were
   `body.dark`-scoped and had been unreachable since the theme phase switched
   Metronic's theme component off. */
#breadcrumb {
  min-width: 0;
}

/* Each crumb is its own flex item, so the trail runs out of room one crumb at
   a time rather than being cut off mid-word at the container's edge. The
   three declarations are one idea and stay together — `text-overflow` does
   nothing without `white-space`, and a utility for that half on the markup
   would put it out of a reader's sight (and pin a class list that two Feature
   tests match on). */
.bread-item {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bread-item,
.bread-item a {
  color: var(--as-text-secondary);
}

.bread-last,
.bread-last a {
  color: var(--as-text);
}

/* --------------------------------------------------------------------------
   5.3 Sidebar
   --------------------------------------------------------------------------
   Navy in every theme, by the reference's own decision — so these rules read
   `--as-sidebar-*`, which carry no theme overrides, and never the surface
   tokens. The reference writes the two translucent whites as `white/10` and
   `white/85` utilities; here they are `--as-sidebar-hover` (which is that
   value) and a mix off `--as-sidebar-text`, so a future sidebar colour change
   is one token. */
.sidebar-appslate {
  width: var(--as-sidebar-current);
  background-color: var(--as-sidebar);
  color: var(--as-sidebar-text);
  transition: width 200ms ease-out;
  /* `fixed top-0 bottom-0` at the left edge, so it is the element a landscape
     notch and a home indicator both reach — the footer control is the last
     thing in it, and would otherwise sit under the bar (§9). */
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
}

/* The logo row, the same height as the top bar beside it so the two rules
   across the chrome line up. */
.sidebar-appslate-header {
  height: var(--as-header-height);
  flex-shrink: 0;
  border-bottom: 1px solid var(--as-sidebar-hover);
}

/* Two `shrink-0`s from the Metronic shell, undone, because the rail needs both
   axes to give:

   vertically, `.sidebar-content` refusing to yield height pushed the footer
   control off the bottom of the screen (the scrollable inside it measures what
   is left, which is why the footer is also named in its dependency list);

   horizontally, `#sidebar_scrollable` kept its 260px through the collapse, so
   at 72px the nav simply overflowed the rail — the old stylesheet clipped that
   overflow instead of narrowing it, which is fine for a full-bleed active row
   and wrong for a pill. */
.sidebar-appslate .sidebar-content {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
}

.sidebar-appslate #sidebar_scrollable,
.sidebar-appslate-nav {
  flex-shrink: 1;
  min-width: 0;
}

.sidebar-appslate-logo .small-logo {
  display: none;
}

.sidebar-appslate-nav {
  padding: 0.75rem 0.75rem 1.25rem;
}

/* Group headings — the reference's `text-[10px] font-semibold uppercase
   tracking-[0.16em] text-white/40` row. The chevron is ours: these groups
   collapse, and the reference's do not.

   Scoped, not bare. The heading is also a `.menu-item`, and `.menu-item` is
   `flex-direction: column` — one class each, so whichever rule comes later
   wins. That used to be this one by virtue of being in the later-loading file;
   now that `.menu-item` has moved into section 6.0 it is not, and an unscoped
   rule here would stack the label above the chevron on every group in the
   sidebar. The attribute appears nowhere outside `.sidebar-appslate`, so the
   scope costs nothing and the two rules are one. */
.sidebar-appslate [data-sidebar-category] {
  display: flex;
  flex-direction: row;
  align-items: center;
  cursor: pointer;
  user-select: none;
  margin-top: 1rem;
  padding: 0 0.75rem 0.25rem;
}

/* The app-chooser branch of the nav (no page menu yet) heads its list with a
   plain `.menu-heading` rather than a collapsible category, so it needs the
   same box the category rows get. Scoped to the sidebar because the same
   class labels the empty state of the header's apps dropdown. */
.sidebar-appslate .menu-heading {
  display: block;
  padding: 0 0.75rem 0.25rem;
}

.sidebar-appslate [data-sidebar-category]:first-child {
  margin-top: 0;
}

.sidebar-appslate .sidebar-category-label {
  color: var(--as-sidebar-muted);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  line-height: 1.4;
  transition: color 0.15s ease;
}

.sidebar-appslate .sidebar-category-chevron {
  color: var(--as-sidebar-muted);
  flex-shrink: 0;
  margin-left: auto;
  transition: transform 0.3s ease, color 0.15s ease;
}

.sidebar-appslate [data-sidebar-category]:hover .sidebar-category-label,
.sidebar-appslate [data-sidebar-category]:hover .sidebar-category-chevron {
  color: var(--as-sidebar-text);
}

.sidebar-appslate [data-sidebar-category].collapsed .sidebar-category-chevron {
  transform: rotate(-90deg);
}

/* Nav rows. The box lives here rather than in arbitrary-value utilities on the
   markup so the collapsed rail can re-centre it with one override — a utility
   would win over any rule in this file, the generated layer loading last. */
.sidebar-appslate .menu-link {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  color: color-mix(in srgb, var(--as-sidebar-text) 85%, transparent);
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}

/* `.menu-icon` carries a 20px indent and a 20px gutter for the dropdown menus
   that share the class — the sidebar draws its own box, so both are zeroed
   here rather than fought with a utility. The colour follows the row: until
   this phase the shared rule pinned every icon to a fixed `#464852`, with an
   `!important` that only the sidebar's inline `<style>` could out-shout, and
   that grey is nearly invisible on a dark surface. */
.sidebar-appslate .menu-link .menu-icon {
  padding-left: 0;
  margin-right: 0;
}

.sidebar-appslate .menu-link .menu-icon,
.sidebar-appslate .menu-link .menu-title {
  color: inherit;
}

.sidebar-appslate .menu-link:hover {
  background-color: var(--as-sidebar-hover);
  color: var(--as-sidebar-text);
}

.sidebar-appslate .menu-item.active > .menu-link,
.sidebar-appslate .menu-item.active .menu-link {
  background-color: var(--as-sidebar-active);
  color: var(--as-sidebar-text);
  font-weight: 500;
}

/* Favourite rows: the title is user data and unbounded, and the cap is stated
   as a width rather than left to `min-width: 0` propagation because the row
   has a fixed-size tile and a grip to leave room for either way. `display:
   block` is needed because `.menu-title` is `display: flex`, which would make
   the text an anonymous flex item and silently disable the ellipsis. */
.sidebar-appslate .sidebar-fav-item .menu-title {
  display: block;
  max-width: calc(var(--as-sidebar-current) - 5.5rem);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The drag grip on a favourite row: hidden until the row (or the grip itself)
   is hovered, and while a drag is in progress so the ghost stays readable.
   The cursor flips to grabbing once Sortable has a hold. */
.sidebar-fav-item .sidebar-fav-grip {
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}

.sidebar-fav-item:hover .sidebar-fav-grip {
  opacity: 1;
  pointer-events: auto;
}

.sidebar-fav-item.sortable-chosen .sidebar-fav-grip {
  cursor: grabbing;
}

.sidebar-fav-item.sortable-ghost {
  opacity: 0.35;
}

/* Collapsed categories (ours, not the reference's) — the click handler adds
   this to every row between one heading and the next. */
.sidebar-cat-hidden {
  display: none;
}

/* The footer rail control. It replaces the round tab that used to float on the
   sidebar's outer edge; the reference gives the collapse its own bordered row
   at the bottom of the nav, and on the rail it becomes the icon alone. */
.sidebar-appslate-footer {
  display: none;
  flex-shrink: 0;
  border-top: 1px solid var(--as-sidebar-hover);
  padding: 0.5rem;
}

@media (min-width: 1024px) {
  .sidebar-appslate-footer {
    display: block;
  }
}

.sidebar-appslate-collapse {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.625rem;
  width: 100%;
  height: 2.25rem;
  padding: 0 0.75rem;
  border: 0;
  border-radius: 0.5rem;
  background-color: transparent;
  color: color-mix(in srgb, var(--as-sidebar-text) 70%, transparent);
  font-size: 1.125rem;
  cursor: pointer;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.sidebar-appslate-collapse:hover {
  background-color: var(--as-sidebar-hover);
  color: var(--as-sidebar-text);
}

.sidebar-appslate-collapse-label {
  font-size: 0.875rem;
  white-space: nowrap;
}

/* Which of the two rail glyphs and which of the two labels are shown, off the
   same body class the width follows — the same idiom as the theme control
   above, and for the same reason: no script has to keep a control in step with
   a state CSS already knows. The glyph selectors carry the ancestor class so
   they outrank `svg.icon:not(.hidden)` in styles.css rather than tying with
   it. */
body svg.icon[data-as-rail-icon='expand'],
body.sidebar-collapse svg.icon[data-as-rail-icon='collapse'] {
  display: none;
}

body.sidebar-collapse svg.icon[data-as-rail-icon='expand'] {
  display: inline-block;
}

[data-as-rail-label='expand'] {
  display: none;
}

body.sidebar-collapse [data-as-rail-label='collapse'] {
  display: none;
}

body.sidebar-collapse [data-as-rail-label='expand'] {
  display: inline;
}

/* --- The collapsed rail --------------------------------------------------
   Everything below is the rail state, and every rule is scoped
   `:not(.content-hover)`: hovering the nav peeks the full sidebar back open
   over the page, so the rail rules have to stop applying rather than be undone
   one by one. `.animating` and `.toggle-hover` are set by the layout script
   and the hover handler — the first while the width transition runs, the
   second while the pointer is on the control itself, and neither should trip
   the peek. */
@media (min-width: 1024px) {
  body.sidebar-collapse .sidebar-appslate.content-hover:not(.animating):not(.toggle-hover) {
    width: var(--as-sidebar-width);
  }

  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .menu-title,
  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .sidebar-fav-grip,
  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .default-logo {
    display: none;
  }

  /* Clipped, not removed: the rail's collapse button is icon-only, and its
     accessible name is this label. */
  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .sidebar-appslate-collapse-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .sidebar-appslate-logo .small-logo {
    display: block;
  }

  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .sidebar-appslate-header {
    justify-content: center;
    padding-left: 0;
    padding-right: 0;
  }

  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .menu-link,
  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .sidebar-appslate-collapse {
    justify-content: center;
    gap: 0;
    padding-left: 0;
    padding-right: 0;
  }

  /* The favourite tile carries a left margin that lines it up under the nav
     icons when there is a label beside it; on the rail there is not. */
  body.sidebar-collapse .sidebar-appslate:not(.content-hover) .sidebar-fav-item .menu-link > span:first-child {
    margin-left: 0;
  }

  /* A group heading with nothing legible in it becomes the divider between
     groups — which is the structure the label was carrying. The reference
     simply drops the label on the rail; a rule of its own is what keeps the
     groups tellable apart at 72px. */
  body.sidebar-collapse .sidebar-appslate:not(.content-hover) [data-sidebar-category] {
    height: 0;
    margin: 0.75rem 0.25rem 0.5rem;
    padding: 0;
    overflow: hidden;
    border-top: 1px solid var(--as-sidebar-hover);
    cursor: default;
  }

  body.sidebar-collapse .sidebar-appslate:not(.content-hover) [data-sidebar-category]:first-child {
    margin-top: 0.25rem;
  }
}

/* --------------------------------------------------------------------------
   5.4 Page header
   --------------------------------------------------------------------------
   The reference's `PageHeader`: display-face title, secondary description,
   right-aligned action cluster, and a fade-up on entry. */
.page-header {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  justify-content: space-between;
  padding-bottom: 0.75rem;
}

@media (min-width: 1024px) {
  .page-header {
    flex-direction: row;
    align-items: flex-start;
  }
}

.page-header-titles {
  min-width: 0;
  max-width: 42rem;
}

.page-header-description {
  margin-top: 0.375rem;
  color: var(--as-text-secondary);
  line-height: 1.625;
}

/* The sibling-page tabs. Ours, not the reference's — it has no second level of
   page navigation — so this is the card treatment applied to the strip we
   already ship. */
.page-tabs {
  position: relative;
  width: 100%;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow);
}

.page-tabs-link {
  padding: 0.875rem 1.25rem;
  border-bottom: 2px solid transparent;
  color: var(--as-text-secondary);
  font-size: 0.875rem;
  font-weight: 500;
  white-space: nowrap;
  flex-shrink: 0;
  transition: color 0.15s ease, border-color 0.15s ease;
}

.page-tabs-link:hover {
  color: var(--as-text);
}

.page-tabs-link.active {
  border-bottom-color: var(--as-primary);
  color: var(--as-primary);
}

/* The two scroll arrows the strip grows when the tabs overflow. They sit on
   top of the tabs, so they carry the strip's own background rather than a
   `bg-white` literal that turned into a light slab on a dark theme. */
.page-tabs-scroll {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 20;
  width: 2.5rem;
  align-items: center;
  justify-content: center;
  background-color: var(--as-bg-elevated);
  color: var(--as-text-secondary);
  pointer-events: auto;
}

.page-tabs-scroll:hover {
  color: var(--as-text);
}

.page-tabs-scroll-left {
  left: 0;
  border-top-left-radius: var(--as-radius);
  border-bottom-left-radius: var(--as-radius);
  box-shadow: 2px 0 8px rgb(0 0 0 / 10%);
}

.page-tabs-scroll-right {
  right: 0;
  border-top-right-radius: var(--as-radius);
  border-bottom-right-radius: var(--as-radius);
  box-shadow: -2px 0 8px rgb(0 0 0 / 10%);
}

/* The strip scrolls with the arrows, never a visible bar. `scrollbar-width`
   covers Firefox and is set on the element; this is the WebKit half, and a
   pseudo-element cannot be expressed as an inline style — which is why this
   one rule used to be a `<style>` block inside the toolbar partial. */
#nav-tabs-container::-webkit-scrollbar {
  display: none;
}

/* --------------------------------------------------------------------------
   5.5 The panel shell
   --------------------------------------------------------------------------
   The two-pane content layout most non-mocked pages are built on: a sidebar
   list beside a detail pane, both card surfaces, sized to the content-height
   budget 5.1 declares. It is chrome rather than page CSS — the agents,
   intelligence, workflow, pattern and object-detail surfaces all reach for it,
   and `.panel-container > *` in section 9 has always assumed it exists.

   The panes used to carry `font-size: 1.125rem`, a Metronic-era base that every
   descendant inherited, so 38 views rendered their unsized text at 18px while
   the rest of the app rendered 14px. It is REMOVED: the reference
   (`/sites/frontend/src/index.css`) sets `body { font-size: 14px }` and then
   sets a font size on no container at all — size is a per-element decision
   there, expressed as a utility. The panes inherit the body now, which is the
   same rule, and any pane that genuinely wants larger type says so on the
   element. Owner decision, 2026-08-10, on the standing rule that the reference
   overrules Metronic.
   -------------------------------------------------------------------------- */

.panel-container {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  width: 100%;
  max-width: 100%;
}

.panel-left,
.panel-right,
.panel-main,
.panel-full {
  background-color: var(--as-bg-elevated);
  border-radius: 8px;
  box-shadow: var(--as-shadow-hairline);
  text-align: left;
  color: var(--as-text);
}

/* Chrome-free chat surfaces (/agents/agent, /agents/workflow-chat) wrap the
   chat layout directly in a `.panel-full`; unlike list/detail panels they
   should read as transparent (matching the project page's
   `.project-detail-panel`), so drop the card background and shadow. */
.panel-full.panel-full-bare {
  background-color: transparent;
  box-shadow: none;
}

/* Pages that hide the nav toolbar (`$hideNavToolbar`) tag their `#content_main`
   with `.no-header`. The default `--as-content-height` budget assumes the
   toolbar renders; with it gone, re-base the budget to the remaining real
   chrome — main's pt-4 (16px) plus ~10px of breathing room — so full-height
   layouts run to ~10px above the viewport bottom instead of stranding the
   toolbar's space as whitespace under the panel. */
#content_main.no-header {
  --as-content-height: calc(100vh - var(--as-header-height) - 26px);
}

/* The global footer is an empty py-5 spacer (~40px); on no-header pages it
   would push the re-based full-height layout past the viewport and force a
   scrollbar, so drop it. `:has()` reacts to AJAX page swaps (`#page_body`
   replacement), unlike a server-side flag which would strand the footer
   hidden or shown across soft navigations. */
body:has(#content_main.no-header) .footer {
  display: none;
}

.panel-left,
.panel-right {
  height: fit-content;
}

.panel-main {
  height: 100%;
  overflow: hidden;
}

.panel-main:has(.detail-tab-content) {
  display: flex;
  flex-direction: column;
}

.panel-full {
  width: 100%;
  grid-column: 1 / -1;
}

.no-bg {
  background-color: transparent;
}

.panel-left--scrollable,
.panel-main--scrollable {
  display: flex;
  flex-direction: column;
}

.panel-main--scrollable {
  overflow-y: auto;
}

.panel-left--narrow {
  max-width: 240px;
}

.panel-container--compact {
  padding-left: 10px;
}

.panel-container--zero {
  padding-left: 0;
}

.panel-container.hide-sidebar .panel-left {
  display: none;
}

.panel-container.hide-sidebar .panel-main {
  grid-column: 1 / -1;
}

/* A panel that is also a card grid must not let a wide table stretch the
   track — the pane scrolls instead. */
.card.card-grid.panel-main {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
}

.card.card-grid.panel-left,
.card.card-grid.panel-right {
  width: 100%;
  max-width: 100%;
}

.card.card-grid.custom-card:not(.card-3) {
  width: 100%;
  max-width: 100%;
}

/* --- The panel tracks ------------------------------------------------------
   Single column until the tablet breakpoint, then a fixed-ish list column
   beside a fluid detail column. `.grid-12` is the twelve-column variant the
   pages with a third (right) pane use. */
@media (min-width: 768px) {
  .panel-container {
    grid-template-columns: minmax(320px, 380px) 1fr;
    grid-template-rows: 1fr;
    min-height: var(--as-content-height);
    gap: 20px;
  }

  .panel-container .panel-left,
  .panel-container .panel-main,
  .panel-container .panel-right {
    grid-row: 1;
    width: 100%;
    height: 100%;
  }

  .panel-container .panel-left {
    grid-column: 1;
  }

  .panel-container .panel-main,
  .panel-container .panel-right {
    grid-column: 2;
  }

  .panel-container .panel-full {
    grid-column: 1 / -1;
  }

  .panel-left--narrow {
    grid-template-columns: minmax(240px, 280px) 1fr;
  }

  .panel-container.grid-12 {
    grid-template-columns: repeat(12, 1fr);
    grid-auto-rows: 1fr;
    gap: 20px;
  }

  .panel-container.grid-12 .panel-left.hidden ~ .panel-main {
    grid-column: 1 / -1;
  }
}

@media (min-width: 1024px) {
  .panel-container {
    grid-template-columns: minmax(360px, 420px) 1fr;
    grid-template-rows: 1fr;
  }

  .panel-container .panel-left,
  .panel-container .panel-main,
  .panel-container .panel-right {
    grid-row: 1;
    height: 100%;
  }

  .panel-container .panel-left {
    grid-column: 1;
  }

  .panel-container .panel-main,
  .panel-container .panel-right {
    grid-column: 2;
  }
}

/* The twelve-column tracks are identical at all three breakpoints; the frozen
   sheet repeated them once per media query, which is what the single
   `min-width: 768px` block below replaces. */
@media (min-width: 768px) {
  .panel-container.grid-12 .panel-left,
  .panel-container.grid-12 .panel-right,
  .panel-container.grid-12[data-panel-right-visible='true'] .panel-left {
    grid-column: span 3;
    grid-row: 1;
  }

  .panel-container.grid-12 .panel-main {
    grid-column: span 9;
    grid-row: 1;
  }

  .panel-container.grid-12[data-panel-right-visible='true'] .panel-main {
    grid-column: span 6;
  }
}

/* ==========================================================================
   6. Components
   ========================================================================== */

/* Buttons, inputs, cards, pills, modals and toasts move here as each family is
   restyled — and its superseded styles.css rules are deleted in that commit. */

/* --------------------------------------------------------------------------
   6.0 The framework component families
   --------------------------------------------------------------------------
   Metronic's own components, moved out of the frozen sheet whole. The design
   reference never mocked these surfaces, so unlike everything below them this
   is a MOVE and not a restyle: values are the ones that were rendering, with
   every `var(--tw-…)` swapped for the `--as-*` name the bridge already pointed
   it at. Four values had no twin and became extension tokens rather than
   literals (`--as-shadow-hairline`, `--as-shadow-raised`, `--as-tooltip-bg`,
   `--as-surface-flat` — construction in `docs/design-token-map.md`).

   POSITION IS LOAD-BEARING, in two directions.

   Before everything else in this file. `styles.css` loads first, so today one
   of these rules loses every same-specificity tie to a rule in `appslate.css`.
   Moving them here inverts that unless they stay above. Sixteen markup sites
   depend on it — `.card` under `.stat-tile` and `.agent-card`, `.badge` and
   `.badge-sm` under the `.trend-*` pills, `.menu-toggle` under `.btn` and
   `.user-chip`, `.menu-dropdown` under `.menu-default`, `.scrollable-x` under
   `.view-content`, `.container-fluid` under `.project-view-context`,
   `.text-2xs` under `.select` — and none of them names both rules, so the
   ordering is the only thing holding them.

   Internally, in the frozen sheet's own order. The families come first and the
   semantic colour utilities after, because that is how `styles.css` had them
   (components ~10,200, utilities ~13,000) and a `bg-primary` on a `.badge` has
   always won. Those utilities read like they belong in section 7; they cannot
   go there, because section 7 sits after the restyled families and they would
   then outrank those too.

   Rules whose declarations were already dead were not carried: the app's own
   `.tab` / `.tabs` block further down `styles.css` redeclares Metronic's
   colour, padding, font-weight and bottom border at the same specificity, so
   only what survived it is here. Rules for a class no markup writes were not
   carried at all — `.menu-label`, `.menu-bullet`, `.menu-badge`,
   `.menu-arrow`, `.card-table`, `.scrollable-hover`, `.scrollable-auto`,
   `.scrollable-x-hover`, `.rating*`, `.accordion*`, `.datatable-*`,
   `.format*`, `.apexcharts-*` and the `.dark`-scoped twins of all of them.
   -------------------------------------------------------------------------- */

/* Metronic's semantic ring tint, the one `border-*` colour utility of its
   family that markup still writes (`qc.blade.php`). It sits above the
   components because it did in the frozen sheet — it is at line 3,632 there,
   before every rule below. */
.border-info-clarity {
  border-color: var(--as-info-muted);
}

/* --- The menu -------------------------------------------------------------
   KTMenu's structure. The SKIN — panel, rows, states — is the restyled
   `.menu-dropdown` / `.menu-default` block further down this file; this is the
   flex scaffold under it, plus the accordion's indent ladder. */
.menu {
  display: flex;
}

/* `.menu-item, .menu-link { padding: 0; margin: 0 }` was NOT carried. It was a
   no-op that survived as a tiebreak: the two classes land on `<a>`, `<div>`,
   `<span>` and `<button>` only, none of which has a UA padding or margin that
   Preflight has not already zeroed. Kept, it would have inverted four live
   utility overrides — `px-2` and `lg:pb-4` on the nav's links, `pb-px` and
   `pt-2.25` on the app-list's items — which beat it today only because the
   frozen sheet puts its utilities after its components. */
.menu-item {
  display: flex;
  flex-direction: column;
}

.menu-link {
  cursor: pointer;
  display: flex;
  align-items: center;
  flex-grow: 1;
}

.menu-toggle {
  display: flex;
  align-items: center;
  flex-grow: 1;
  line-height: 1;
}

.menu-title {
  display: flex;
  align-items: center;
  line-height: 1;
  flex-grow: 1;
}

.menu-icon {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  /* The 20px indent and gutter the dropdown menus are built around. Both are
     overridden by more specific rules where they do not belong — the sidebar
     draws its own box (section 5.3) and `.menu-default` sets its own 10px
     gutter below — so this pair only ever reaches a bare dropdown item. */
  padding-left: 1.25rem;
  margin-right: 1.25rem;
}

.menu-dropdown,
.menu-accordion {
  padding: 0;
  margin: 0;
  display: none;
  align-items: stretch;
  flex-direction: column;
  z-index: 10;
}

/* The list-table's row menu is `data-menu-fixed` and has to clear a sticky
   table header, so its markup writes `z-10000` beside `.menu-dropdown`. That
   utility used to win on source order — the frozen sheet puts its utilities
   after its components — and nothing else was holding it: a generated `z-10000`
   is inside `@layer utilities` and loses to any unlayered rule, so the
   intent would have evaporated the moment `styles.css` went either way. It is
   a rule now, at one class more than the base. */
.menu-dropdown.z-10000 {
  z-index: 10000;
}

/* The two branches that named `.base-Popper-root > .menu-container` and
   `.menu-dropdown.menu.show` went with the port: no markup writes either, and
   KTMenu opens a dropdown by putting `.show` on the item. */
.show.menu-item-dropdown > .menu-dropdown,
.menu-dropdown.show[data-popper-placement] {
  display: flex;
  will-change: transform;
}

.menu-accordion {
  display: none;
  transition: height 0.3s ease;
}

.show:not(.menu-dropdown) > .menu-accordion,
.transitioning:not(.menu-dropdown) > .menu-accordion,
.menu-accordion.show {
  display: flex;
}

.menu-default .menu-accordion:not(.menu-no-indent) .menu-item > .menu-link {
  margin-left: 1.25rem;
}

.menu-default
  .menu-accordion:not(.menu-no-indent)
  .menu-item
  > .menu-accordion
  .menu-item
  > .menu-link {
  margin-left: 2rem;
}

.menu-default
  .menu-accordion:not(.menu-no-indent)
  .menu-item
  > .menu-accordion
  .menu-item
  > .menu-accordion
  .menu-item
  > .menu-link {
  margin-left: 2.75rem;
}

/* The plain text link. Sits with the menu because that is where the frozen
   sheet had it. */
.link {
  color: var(--as-primary);
}

.link:hover {
  color: var(--as-primary-hover);
}

/* --- Native control extras ------------------------------------------------
   The `.select` / `.checkbox` / `.radio` skins are the restyled form-control
   block further down; these are the two members that block never covered. */
.select-wide {
  width: auto !important;
  min-width: max-content !important;
  flex-shrink: 0 !important;
  padding-right: 2.25rem !important;
}

/* The scale slider `UI::form_html` emits for a scale-typed property. */
.range {
  width: 100%;
  height: 0.5rem;
  padding: 0;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}

.range:focus {
  outline: 0;
}

.range::-moz-focus-outer {
  border: 0;
}

.range::-webkit-slider-thumb {
  width: 1.625rem;
  height: 1.625rem;
  margin-top: -0.563rem;
  box-shadow: var(--as-shadow-raised);
  -webkit-appearance: none;
  appearance: none;
  border: 1px solid var(--as-gray-200);
  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
    box-shadow 0.15s ease-in-out;
  border-radius: 50%;
  background-color: var(--as-surface-flat);
}

.range::-webkit-slider-thumb:active {
  background-color: var(--as-surface-flat);
}

.range::-moz-range-thumb {
  width: 1.625rem;
  height: 1.625rem;
  margin-top: -0.563rem;
  box-shadow: var(--as-shadow-raised);
  -moz-appearance: none;
  appearance: none;
  border: 1px solid var(--as-gray-200);
  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
    box-shadow 0.15s ease-in-out;
  border-radius: 50%;
  background-color: var(--as-surface-flat);
}

.range::-moz-range-thumb:active {
  background-color: var(--as-surface-flat);
}

.range::-moz-range-track {
  width: 100%;
  height: 0.5rem;
  color: transparent;
  cursor: pointer;
  background-color: var(--as-gray-200);
  border-color: transparent;
  border-radius: 0.188rem;
}

.range::-webkit-slider-runnable-track {
  width: 100%;
  height: 0.5rem;
  color: transparent;
  cursor: pointer;
  background-color: var(--as-gray-200);
  border-color: transparent;
  border-radius: 0.188rem;
}

.range:disabled {
  pointer-events: none;
}

.range:disabled::-webkit-slider-thumb {
  background-color: var(--as-gray-100);
}

.range:disabled::-moz-range-thumb {
  background-color: var(--as-gray-100);
}

/* --- The page container ---------------------------------------------------- */
.container-fixed {
  flex-grow: 1;
  width: 100%;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

.container-fluid {
  width: 100%;
  flex-grow: 1;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}

@media (min-width: 1280px) {
  .container-fixed {
    margin-left: auto;
    margin-right: auto;
    padding-left: 1.875rem;
    padding-right: 1.875rem;
    max-width: 1280px;
  }

  .container-fluid {
    padding-left: 1.875rem;
    padding-right: 1.875rem;
  }
}

/* --- The file field's visible box ------------------------------------------
   `UI::form_html` wraps an invisible `<input type="file">` in this so the
   label beside it can be the control. Section 4.2's note names the same
   element: the 11px size here is what the dropped `[type='file'] { font-size:
   unset }` used to protect. */
.image-input {
  display: inline-flex;
  position: relative;
  align-items: stretch;
  justify-content: center;
  width: 100%;
  font-size: 0.6875rem;
}

.image-input input[type='file'] {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  position: absolute;
  width: 0 !important;
  height: 0 !important;
  overflow: hidden;
  opacity: 0;
}

/* The drop target's active state (`project.blade.php`'s uploader). */
input[type='file'].drag-active {
  border-color: #4caf50;
  background-color: rgba(76, 175, 80, 0.1);
}

/* --- The dialog ------------------------------------------------------------
   Mechanics only. The box's skin is the restyled dialog block below, and the
   viewport rules that keep a full-screen dialog reachable on a phone are in
   section 9. */
.modal {
  width: 100%;
  display: none;
  opacity: 0;
  position: fixed;
  overflow: auto;
  inset: 0;
  transition: opacity 300ms ease;
}

.modal.open {
  opacity: 1;
  transition: opacity 300ms ease;
}

.modal-content {
  position: relative;
  margin-left: auto;
  margin-right: auto;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius-lg);
  box-shadow: var(--as-shadow);
  background-color: var(--as-bg-elevated);
}

.modal-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.625rem;
}

.modal-body {
  padding: 1.25rem;
  background-color: var(--as-bg);
}

.modal-body > :last-child {
  margin-bottom: 0;
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  background-color: rgb(0 0 0 / 50%);
  backdrop-filter: blur(2px);
}

/* KTDrawer's scrim. Darker than the dialog's on purpose — it is the frozen
   sheet's `--tw-backdrop-background-color`, and a drawer covers less of the
   page than a dialog does. */
.drawer-backdrop {
  position: fixed;
  inset: 0;
  background-color: rgba(0, 0, 0, 0.8);
}

/* --- The tooltip -----------------------------------------------------------
   One live consumer, the network view's hover card, which overrides most of
   this from `networkviz.css`. Carried whole anyway: the class is KTTooltip's
   and the JS plan keeps the name. */
.tooltip {
  display: none;
  color: white;
  box-shadow: var(--as-shadow-hairline);
  background-color: var(--as-tooltip-bg);
  border: 0;
  border-radius: 0.5rem;
  padding: 0.375rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 400;
  line-height: 1rem;
}

.tooltip.show {
  display: block;
}

/* --- The button group ------------------------------------------------------
   Structure only; the pressed state is the restyled `.btn-group .btn.active`
   rule below. */
.btn-group {
  display: flex;
  align-items: stretch;
}

.btn-group .btn + .btn {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  border-left: 0;
}

.btn-group .btn:has(+ .btn) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* --- Tabs ------------------------------------------------------------------
   Two rules per selector until pass (c) step 4: Metronic's layout here and the
   app's own skin (the raised, rounded strip) in the frozen sheet, which won
   every shared declaration by being later. The two are merged now — Metronic's
   layout, the app's chrome — so each selector is declared once.

   The skin's four literals joined the tokens on the way in: `#ddd` → the
   design border, `#666` → the mid grey step, `#2196f3` → the brand blue. That
   last one also settles a state the frozen arrangement got wrong — hovering an
   ALREADY-ACTIVE tab used to jump from material blue to brand blue, because
   `.tab:hover` here and `.tab.active` there tie and order decided it. Both are
   the brand blue now, so the jump is gone. */
.tabs {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  border-bottom: 1px solid var(--as-border);
  margin-bottom: 15px;
}

.tab {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8125rem;
  line-height: 1;
  padding: 12px 24px;
  margin-right: 5px;
  cursor: pointer;
  border: 1px solid transparent;
  border-bottom: none;
  border-radius: var(--as-radius) var(--as-radius) 0 0;
  font-weight: 500;
  color: var(--as-gray-600);
  transition: all 0.2s ease;
}

.tab i {
  font-size: 0.9375rem;
  color: var(--as-gray-600);
}

.tab:hover,
.tab:focus {
  color: var(--as-primary);
  background-color: var(--as-bg-muted);
}

.tab.active {
  background-color: var(--as-bg-elevated);
  border-color: var(--as-border);
  color: var(--as-primary);
  margin-bottom: -1px;
  padding-bottom: 13px;
}

.tab.active i,
.tab:hover i,
.tab:focus i {
  color: var(--as-primary);
}

/* --- Pagination ------------------------------------------------------------
   The list footer's page buttons. Each rule is one step more specific than the
   button family below it, which is what lets a `.btn` inside `.pagination` be
   a 30px square instead of the ladder's 32px. */
.pagination {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
}

.pagination .btn {
  display: inline-flex;
  flex-shrink: 0;
  justify-content: center;
  height: 1.875rem;
  width: 1.875rem;
  font-size: 0.8125rem;
  padding: 0;
  line-height: 0;
  color: var(--as-gray-600);
}

.pagination .btn i {
  font-size: 1rem;
  color: var(--as-gray-700);
}

.pagination .btn:hover,
.pagination .btn:focus,
.pagination .btn:active,
.pagination .btn.active {
  background-color: var(--as-gray-200);
  color: var(--as-gray-800);
}

.pagination .btn:hover i,
.pagination .btn:focus i,
.pagination .btn:active i,
.pagination .btn.active i {
  color: var(--as-gray-900);
}

.pagination .btn[disabled],
.pagination .btn.disabled {
  opacity: 1;
  pointer-events: none;
  color: var(--as-gray-500);
}

.pagination .btn[disabled] i,
.pagination .btn.disabled i {
  color: var(--as-gray-400);
}

/* --- The card --------------------------------------------------------------
   The app's most-used surface. `--as-shadow-hairline` is the frozen sheet's
   own `--tw-card-box-shadow`, not the design's `--as-shadow`: swapping those
   is the card restyle's decision and would be visible on every page. */
.card {
  display: flex;
  flex-direction: column;
  box-shadow: var(--as-shadow-hairline);
  background-color: var(--as-bg-elevated);
  border-radius: 0.75rem;
  border: 1px solid var(--as-gray-200);
}

.card-title {
  font-size: 1.125rem;
  line-height: 1.5rem;
  font-weight: 500;
  color: var(--as-gray-900);
}

.card-header {
  display: flex;
  min-height: 56px;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--as-gray-200);
  padding-left: 1.5rem;
  padding-right: 1.5rem;
  padding-top: 0.75rem;
  padding-bottom: 0rem;
}

/* The companion is the `.x.hidden` idiom from section 4.3, applied to the one
   utility a `.card-header` is written with that the component contradicts.
   Four views head a list card with `flex flex-col items-stretch` so the search
   row and the toolbar fill the bar; `align-items: center` above would centre
   them at 273px instead. The utility beat the component in the frozen sheet by
   source order — and a GENERATED `items-stretch` never could, being layered —
   so the override needs a rule to survive either file going away. */
.card-header.items-stretch {
  align-items: stretch;
}

.card-body {
  flex-grow: 1;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
  padding-top: 1.25rem;
  padding-bottom: 1.25rem;
}

.card-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-left: 1.875rem;
  padding-right: 1.875rem;
  padding-top: 0.75rem;
  padding-bottom: 0.75rem;
}

.card-header + .card-body table th:first-child,
.card-header + .card-body table th:last-child {
  border-radius: 0;
}

.card-grid .card-header,
.card-grid .card-footer {
  padding-left: 1.25rem;
  padding-right: 1.25rem;
}

.card-grid .card-body {
  padding: 0;
}

.card-grid .card-body table {
  border: 0;
}

.card-grid .card-body table th:last-child,
.card-grid .card-body table td:last-child {
  padding-right: 1.25rem;
}

.card-border {
  border: 1px solid var(--as-gray-200);
}

.card-rounded {
  border-radius: 0.75rem;
}

/* --- Tables ----------------------------------------------------------------
   The `.table` base: a rounded, separately-bordered grid with a tinted header
   and a hairline under every row but the last. `.objecttable` (section 6) is
   one step more specific and re-states the header ink and fill for the list
   pages; every other table in the app — the file browser, the intelligence
   views, the admin lists — draws what is here.

   This family arrived at pass (c) step 5, not step 3 with the rest of
   Metronic's components: the reachability pass that chose step 3's scope keyed
   on a rule's own classes, and every rule below qualifies on `.table` plus an
   ELEMENT, so none of them matched. It went unnoticed until step 5 deleted the
   frozen sheet and 377 cells lost their borders and 162 their padding. */
.table {
  width: 100%;
  caption-side: bottom;
  border-collapse: separate;
  border-spacing: 0;
  border-radius: var(--as-radius);
  overflow: hidden;
  vertical-align: middle;
  text-align: left;
  color: var(--as-gray-700);
  font-weight: 500;
  font-size: 0.8125rem;
  line-height: 1.5rem;
}

/* The radius is on the table, which clips it — but the corner CELLS have to
   carry it too, or their background squares off the curve. */
.table th:first-child {
  border-top-left-radius: var(--as-radius);
  padding-left: 1rem;
  padding-right: 1rem;
}

.table th:last-child {
  border-top-right-radius: var(--as-radius);
}

.table th input[type="checkbox"],
.table td input[type="checkbox"] {
  vertical-align: inherit;
}

.table thead td,
.table thead th,
.table tfoot td,
.table tfoot th {
  padding: 0.625rem 0.6rem 0.625rem 0.25rem;
  background-color: var(--as-table-head-bg);
  color: var(--as-gray-600);
  font-weight: 500;
  font-size: 0.8125rem;
  line-height: 1.75rem;
  vertical-align: middle;
}

.table thead td,
.table thead th {
  border-bottom: 1px solid var(--as-gray-200);
}

.table tfoot td,
.table tfoot th {
  border-top: 1px solid var(--as-gray-200);
}

.table tbody {
  vertical-align: inherit;
}

.table tbody tr td,
.table tbody tr th {
  padding: 0.7rem 0.3rem;
  border-bottom: 1px solid var(--as-gray-200);
}

/* The table's own overflow clip draws the bottom edge, so the last row must
   not add a second line inside the curve. */
.table tbody tr:last-child td,
.table tbody tr:last-child th {
  border-bottom: 0;
}

.table-striped td {
  border-left: 0px !important;
  border-right: 0px !important;
}

.table-border {
  border: 1px solid var(--as-gray-200);
}

.table-border td:last-child,
.table-border th:last-child {
  border-right: 0;
}

/* --- The sortable column head ----------------------------------------------
   The two chevrons are a base64 pair rather than a sprite icon, and the
   `.asc` / `.desc` states swap which one is inked. The stroke colours live
   inside the data URIs where no token reaches — the same constraint section
   4.1's select chevron records. */
.sort {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
  line-height: 1;
}

.sort-icon {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 0.875rem;
  width: 0.875rem;
  gap: 0.125rem;
  line-height: 1;
}

.sort-icon:before {
  display: inline-block;
  content: '';
  height: 0.25rem;
  width: 0.438rem;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc4JyBoZWlnaHQ9JzUnIHZpZXdCb3g9JzAgMCA4IDUnIGZpbGw9J25vbmUnPjxwYXRoIGQ9J00xLjA4MzMzIDQuODMzMzNDMC45MDgzMzMgNC44MzMzMyAwLjc5MTY2NyA0Ljc3NSAwLjY3NSA0LjY1ODMzQzAuNDQxNjY3IDQuNDI1IDAuNDQxNjY3IDQuMDc1IDAuNjc1IDMuODQxNjdMMy41OTE2NyAwLjkyNUMzLjgyNSAwLjY5MTY2NyA0LjE3NSAwLjY5MTY2NyA0LjQwODMzIDAuOTI1TDcuMzI1IDMuODQxNjdDNy41NTgzMyA0LjA3NSA3LjU1ODMzIDQuNDI1IDcuMzI1IDQuNjU4MzNDNy4wOTE2NyA0Ljg5MTY3IDYuNzQxNjcgNC44OTE2NyA2LjUwODMzIDQuNjU4MzNMNCAyLjE1TDEuNDkxNjcgNC42NTgzM0MxLjM3NSA0Ljc3NSAxLjI1ODMzIDQuODMzMzMgMS4wODMzMyA0LjgzMzMzWicgZmlsbD0nIzc4ODI5RCcvPjwvc3ZnPg==");
}

.sort-icon:after {
  display: inline-block;
  content: '';
  height: 0.25rem;
  width: 0.438rem;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc4JyBoZWlnaHQ9JzUnIHZpZXdCb3g9JzAgMCA4IDUnIGZpbGw9J25vbmUnPjxwYXRoIGQ9J000IDQuMjQ5ODRDMy44MjUgNC4yNDk4NCAzLjcwODMzIDQuMTkxNSAzLjU5MTY3IDQuMDc0ODRMMC42NzUgMS4xNTgxN0MwLjQ0MTY2NyAwLjkyNDgzOCAwLjQ0MTY2NyAwLjU3NDgzNyAwLjY3NSAwLjM0MTUwNEMwLjkwODMzMyAwLjEwODE3MSAxLjI1ODMzIDAuMTA4MTcxIDEuNDkxNjcgMC4zNDE1MDRMNCAyLjg0OTg0TDYuNTA4MzMgMC4zNDE1MDRDNi43NDE2NyAwLjEwODE3MSA3LjA5MTY3IDAuMTA4MTcxIDcuMzI1IDAuMzQxNTA0QzcuNTU4MzMgMC41NzQ4MzcgNy41NTgzMyAwLjkyNDgzOCA3LjMyNSAxLjE1ODE3TDQuNDA4MzMgNC4wNzQ4NEM0LjI5MTY3IDQuMTkxNSA0LjE3NSA0LjI0OTg0IDQgNC4yNDk4NFonIGZpbGw9JyM3ODgyOUQnLz48L3N2Zz4=");
}

.asc > .sort-icon:before {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc4JyBoZWlnaHQ9JzUnIHZpZXdCb3g9JzAgMCA4IDUnIGZpbGw9J25vbmUnPjxwYXRoIGQ9J00xLjA4MzMzIDQuODMzMzNDMC45MDgzMzMgNC44MzMzMyAwLjc5MTY2NyA0Ljc3NSAwLjY3NSA0LjY1ODMzQzAuNDQxNjY3IDQuNDI1IDAuNDQxNjY3IDQuMDc1IDAuNjc1IDMuODQxNjdMMy41OTE2NyAwLjkyNUMzLjgyNSAwLjY5MTY2NyA0LjE3NSAwLjY5MTY2NyA0LjQwODMzIDAuOTI1TDcuMzI1IDMuODQxNjdDNy41NTgzMyA0LjA3NSA3LjU1ODMzIDQuNDI1IDcuMzI1IDQuNjU4MzNDNy4wOTE2NyA0Ljg5MTY3IDYuNzQxNjcgNC44OTE2NyA2LjUwODMzIDQuNjU4MzNMNCAyLjE1TDEuNDkxNjcgNC42NTgzM0MxLjM3NSA0Ljc3NSAxLjI1ODMzIDQuODMzMzMgMS4wODMzMyA0LjgzMzMzWicgZmlsbD0nIzRCNTY3NScvPjwvc3ZnPg==");
}

.asc > .sort-icon:after {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc4JyBoZWlnaHQ9JzUnIHZpZXdCb3g9JzAgMCA4IDUnIGZpbGw9J25vbmUnPjxwYXRoIGQ9J000IDQuMjQ5ODRDMy44MjUgNC4yNDk4NCAzLjcwODMzIDQuMTkxNSAzLjU5MTY3IDQuMDc0ODRMMC42NzUgMS4xNTgxN0MwLjQ0MTY2NyAwLjkyNDgzOCAwLjQ0MTY2NyAwLjU3NDgzNyAwLjY3NSAwLjM0MTUwNEMwLjkwODMzMyAwLjEwODE3MSAxLjI1ODMzIDAuMTA4MTcxIDEuNDkxNjcgMC4zNDE1MDRMNCAyLjg0OTg0TDYuNTA4MzMgMC4zNDE1MDRDNi43NDE2NyAwLjEwODE3MSA3LjA5MTY3IDAuMTA4MTcxIDcuMzI1IDAuMzQxNTA0QzcuNTU4MzMgMC41NzQ4MzcgNy41NTgzMyAwLjkyNDgzOCA3LjMyNSAxLjE1ODE3TDQuNDA4MzMgNC4wNzQ4NEM0LjI5MTY3IDQuMTkxNSA0LjE3NSA0LjI0OTg0IDQgNC4yNDk4NFonIGZpbGw9JyNDNENBREEnLz48L3N2Zz4=");
}

.desc > .sort-icon:before {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc4JyBoZWlnaHQ9JzUnIHZpZXdCb3g9JzAgMCA4IDUnIGZpbGw9J25vbmUnPjxwYXRoIGQ9J00xLjA4MzMzIDQuODMzMzNDMC45MDgzMzMgNC44MzMzMyAwLjc5MTY2NyA0Ljc3NSAwLjY3NSA0LjY1ODMzQzAuNDQxNjY3IDQuNDI1IDAuNDQxNjY3IDQuMDc1IDAuNjc1IDMuODQxNjdMMy41OTE2NyAwLjkyNUMzLjgyNSAwLjY5MTY2NyA0LjE3NSAwLjY5MTY2NyA0LjQwODMzIDAuOTI1TDcuMzI1IDMuODQxNjdDNy41NTgzMyA0LjA3NSA3LjU1ODMzIDQuNDI1IDcuMzI1IDQuNjU4MzNDNy4wOTE2NyA0Ljg5MTY3IDYuNzQxNjcgNC44OTE2NyA2LjUwODMzIDQuNjU4MzNMNCAyLjE1TDEuNDkxNjcgNC42NTgzM0MxLjM3NSA0Ljc3NSAxLjI1ODMzIDQuODMzMzMgMS4wODMzMyA0LjgzMzMzWicgZmlsbD0nI0M0Q0FEQScvPjwvc3ZnPg==");
}

.desc > .sort-icon:after {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPSc4JyBoZWlnaHQ9JzUnIHZpZXdCb3g9JzAgMCA4IDUnIGZpbGw9J25vbmUnPjxwYXRoIGQ9J000IDQuMjQ5ODRDMy44MjUgNC4yNDk4NCAzLjcwODMzIDQuMTkxNSAzLjU5MTY3IDQuMDc0ODRMMC42NzUgMS4xNTgxN0MwLjQ0MTY2NyAwLjkyNDgzOCAwLjQ0MTY2NyAwLjU3NDgzNyAwLjY3NSAwLjM0MTUwNEMwLjkwODMzMyAwLjEwODE3MSAxLjI1ODMzIDAuMTA4MTcxIDEuNDkxNjcgMC4zNDE1MDRMNCAyLjg0OTg0TDYuNTA4MzMgMC4zNDE1MDRDNi43NDE2NyAwLjEwODE3MSA3LjA5MTY3IDAuMTA4MTcxIDcuMzI1IDAuMzQxNTA0QzcuNTU4MzMgMC41NzQ4MzcgNy41NTgzMyAwLjkyNDgzOCA3LjMyNSAxLjE1ODE3TDQuNDA4MzMgNC4wNzQ4NEM0LjI5MTY3IDQuMTkxNSA0LjE3NSA0LjI0OTg0IDQgNC4yNDk4NFonIGZpbGw9JyM0QjU2NzUnLz48L3N2Zz4=");
}

.sort-label {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

/* --- The badge -------------------------------------------------------------
   Anatomy, size steps and the semantic fills, in the frozen sheet's own order:
   `.badge-xs` / `.badge-sm` reset the padding the base sets, and the colour
   block restates `.badge` after them so a variant's fill lands on top of the
   neutral one. `font-weight: 600` is the D-9 restyle's, folded in here rather
   than left as a second `.badge` rule further down this file.

   The `.status-*` / `.priority-*` pills are NOT this family — see the pill
   anatomy block below, which the badge restyle owns re-cutting.

   The filled variants read an `-inverse` token where the family has one and a
   plain `#ffffff` where it does not; only primary, danger and info ever needed
   a name for the readable colour on their fill. `.badge-secondary`'s two hex
   fallbacks (`#4b5563`, `#e5e7eb`) were dropped: they are Tailwind's grays,
   not ours, and the tokens beside them cannot fail to resolve. */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  border-radius: 0.25rem;
  padding: 0.5rem 0.5rem;
  border: 1px solid transparent;
  font-size: 0.6875rem;
  font-weight: 600;
}

.badge-xs {
  font-size: 0.625rem;
  padding: 0.1875rem 0.3125rem;
}

.badge-sm {
  padding: 0.3125rem 0.375rem;
}

.badge {
  color: var(--as-gray-700);
  border-color: transparent;
  background-color: var(--as-gray-200);
}

.badge-outline {
  color: var(--as-gray-600);
  border-color: var(--as-gray-300);
  background-color: var(--as-gray-100);
}

.badge-primary {
  color: var(--as-primary-inverse);
  background-color: var(--as-primary);
}

.badge-outline.badge-primary {
  color: var(--as-primary);
  background-color: color-mix(in srgb, var(--as-primary) 7%, var(--as-bg-elevated));
  border-color: var(--as-primary-muted);
}

.badge-success {
  color: #fff;
  background-color: var(--as-success);
}

.badge-outline.badge-success {
  color: var(--as-success);
  background-color: color-mix(in srgb, var(--as-success) 7%, var(--as-bg-elevated));
  border-color: var(--as-success-muted);
}

.badge-danger {
  color: var(--as-danger-inverse);
  background-color: var(--as-danger);
}

.badge-outline.badge-danger {
  color: var(--as-danger);
  background-color: color-mix(in srgb, var(--as-danger) 7%, var(--as-bg-elevated));
  border-color: var(--as-danger-muted);
}

.badge-warning {
  color: #fff;
  background-color: var(--as-warning);
}

.badge-secondary {
  color: var(--as-gray-700);
  background-color: var(--as-gray-200);
}

.badge-outline.badge-warning {
  color: var(--as-warning);
  background-color: color-mix(in srgb, var(--as-warning) 7%, var(--as-bg-elevated));
  border-color: var(--as-warning-muted);
}

.badge-info {
  color: var(--as-info-inverse);
  background-color: var(--as-info-filled);
}

.badge-outline.badge-info {
  color: var(--as-info);
  background-color: color-mix(in srgb, var(--as-info) 7%, var(--as-bg-elevated));
  border-color: var(--as-info-muted);
}

/* --- Scroll containers -----------------------------------------------------
   Metronic's thin-scrollbar family, reduced to the six spellings markup uses.
   The thumb colour was `var(--tw-scrollbar-thumb-color, var(--tw-gray-200))`,
   and nothing in the app ever set that variable — the fallback is what has
   always drawn, so the read is gone with it.

   Not merged with `.as-scroll` in section 4.3, which is the design's own
   scrollbar at a different size and colour: that consolidation is a restyle,
   not a move, and it would change every scroll surface in the app. */
.scrollable,
.scrollable-y,
.scrollable-x,
.scrollable-y-hover,
.scrollable-y-auto,
.scrollable-x-auto {
  scrollbar-width: thin;
  scrollbar-color: transparent transparent;
  position: relative;
}

.scrollable::-webkit-scrollbar,
.scrollable-y::-webkit-scrollbar,
.scrollable-x::-webkit-scrollbar,
.scrollable-y-hover::-webkit-scrollbar,
.scrollable-y-auto::-webkit-scrollbar,
.scrollable-x-auto::-webkit-scrollbar {
  width: 0.35rem;
  height: 0.35rem;
}

.scrollable::-webkit-scrollbar-track,
.scrollable-y::-webkit-scrollbar-track,
.scrollable-x::-webkit-scrollbar-track,
.scrollable-y-hover::-webkit-scrollbar-track,
.scrollable-y-auto::-webkit-scrollbar-track,
.scrollable-x-auto::-webkit-scrollbar-track {
  background-color: transparent;
}

.scrollable::-webkit-scrollbar-thumb,
.scrollable-y::-webkit-scrollbar-thumb,
.scrollable-x::-webkit-scrollbar-thumb,
.scrollable-y-hover::-webkit-scrollbar-thumb,
.scrollable-y-auto::-webkit-scrollbar-thumb,
.scrollable-x-auto::-webkit-scrollbar-thumb {
  border-radius: 1.25rem;
}

.scrollable::-webkit-scrollbar-corner,
.scrollable-y::-webkit-scrollbar-corner,
.scrollable-x::-webkit-scrollbar-corner,
.scrollable-y-hover::-webkit-scrollbar-corner,
.scrollable-y-auto::-webkit-scrollbar-corner,
.scrollable-x-auto::-webkit-scrollbar-corner {
  background-color: transparent;
}

/* The thumb is inked here, not above: `.scrollable-y-hover` shows no bar at
   rest and only takes the colour while the pointer is over it. */
.scrollable,
.scrollable-y,
.scrollable-x,
.scrollable-y-auto,
.scrollable-x-auto,
.scrollable-y-hover:hover {
  scrollbar-color: var(--as-gray-200) transparent;
}

.scrollable::-webkit-scrollbar-thumb,
.scrollable-y::-webkit-scrollbar-thumb,
.scrollable-x::-webkit-scrollbar-thumb,
.scrollable-y-auto::-webkit-scrollbar-thumb,
.scrollable-x-auto::-webkit-scrollbar-thumb,
.scrollable-y-hover:hover::-webkit-scrollbar-thumb {
  background-color: var(--as-gray-200);
}

.scrollable {
  overflow: scroll;
}

.scrollable-y,
.scrollable-y-hover {
  overflow-y: scroll;
}

.scrollable-x {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.scrollable-y-auto {
  overflow-y: auto;
}

.scrollable-x-auto {
  overflow-x: auto;
}

/* Tablet and below: a scroll region that always shows its bar eats 6px of a
   narrow column, so the family degrades to `auto`. */
@media (max-width: 1024px) {
  .scrollable {
    overflow: auto;
  }

  .scrollable-y,
  .scrollable-y-hover {
    overflow-y: auto;
  }

  .scrollable-x {
    overflow-x: auto;
  }
}

/* --- The progress bar ------------------------------------------------------ */
.progress {
  width: 100%;
  display: flex;
  min-height: 4px;
  overflow: hidden;
  background-color: var(--as-gray-100);
  border-radius: 0.5rem;
}

.progress-bar {
  display: flex;
  flex-direction: column;
  justify-content: center;
  overflow: hidden;
  text-align: center;
  white-space: nowrap;
  background-color: var(--as-gray-300);
  border-radius: 0.5rem;
}

.progress-primary {
  background-color: color-mix(in srgb, var(--as-primary) 7%, var(--as-bg-elevated));
}

.progress-primary .progress-bar {
  background-color: var(--as-primary);
}

/* --- Metronic's semantic colour utilities ----------------------------------
   `bg-primary`, `text-success`, `border-danger` and their tints LOOK like
   Tailwind and are not: no v4 build produces them, they are the spelling
   `UI::form_html`, the seed SQL and forty-odd views reach for, and they are
   the last thing in the frozen sheet that a class on an element can use to
   recolour a component. They keep their position after the families above,
   which is the position they had.

   `.bg-main` is the page canvas and already read `--as-bg` before the port;
   the `[--tw-page-bg]` twin beside it went with the rules that set it. */
.bg-main {
  background-color: var(--as-bg);
}

.bg-danger {
  background-color: var(--as-danger);
}

.bg-danger-light {
  background-color: color-mix(in srgb, var(--as-danger) 7%, var(--as-bg-elevated));
}

.bg-info {
  background-color: var(--as-info);
}

.bg-info-light {
  background-color: color-mix(in srgb, var(--as-info) 7%, var(--as-bg-elevated));
}

.bg-light {
  background-color: var(--as-surface-flat);
}

.bg-primary {
  background-color: var(--as-primary);
}

.bg-primary-light {
  background-color: color-mix(in srgb, var(--as-primary) 7%, var(--as-bg-elevated));
}

.bg-success {
  background-color: var(--as-success);
}

.bg-success-light {
  background-color: color-mix(in srgb, var(--as-success) 7%, var(--as-bg-elevated));
}

.bg-warning {
  background-color: var(--as-warning);
}

.bg-warning-light {
  background-color: color-mix(in srgb, var(--as-warning) 7%, var(--as-bg-elevated));
}

.border-danger {
  border-color: var(--as-danger);
}

.border-primary {
  border-color: var(--as-primary);
}

.border-warning {
  border-color: var(--as-warning);
}

.text-danger {
  color: var(--as-danger);
}

.text-info {
  color: var(--as-info);
}

.text-primary {
  color: var(--as-primary);
}

.text-success {
  color: var(--as-success);
}

.text-warning {
  color: var(--as-warning);
}

.hover\:text-primary:hover {
  color: var(--as-primary);
}

.hover\:text-primary-active:hover {
  color: var(--as-primary-hover);
}

.group:hover .group-hover\:text-primary-active {
  color: var(--as-primary-hover);
}

/* Metronic's four extra type steps. The generated layer cannot serve these —
   `2sm`, `2xs`, `3xs` and `md` are not Tailwind size names — and 92 markup
   sites write one. The earlier `.text-2xs { font-size: 0.625rem }` at
   `styles.css` line 5,129 was a dead first definition; this is the one that
   was rendering. */
.text-2sm {
  font-size: 0.8125rem;
  line-height: 1.125rem;
}

.text-2xs {
  font-size: 0.6875rem;
  line-height: 0.75rem;
}

.text-3xs {
  font-size: 0.625rem;
  line-height: 0.75rem;
}

.text-md {
  font-size: 0.875rem;
  line-height: 1.375rem;
}

/* Alignment classes stored in the database rather than written in a view:
   `objectproperty.htmlclass` carries them, which is why they are in
   `scripts/css_audit_safelist.txt` and why no grep finds two of the three. */
.txl {
  text-align: left !important;
}

.txc {
  text-align: center !important;
}

.txr {
  text-align: right !important;
}

/* --- The menu's stateful variants ------------------------------------------
   KTMenu's answer to "style this element when its `.menu-item` ancestor is
   active/here/open". The frozen sheet generated 264 of these from the full
   variant matrix; markup writes eight, each in up to five shapes (on the item,
   on its link or toggle, or on a descendant of either). The shapes are one
   rule per utility here — a selector list keeps each branch's own specificity,
   so collapsing them changes nothing but the line count.

   `.menu-item.here > .menu-link .menu-item-here:text-primary` is the one
   member that carries `!important`; that is the frozen sheet's own, not an
   addition. */
.menu-item.active.menu-item-active\:border-b-primary,
.menu-item.active > .menu-link.menu-item-active\:border-b-primary,
.menu-item.active > .menu-link .menu-item-active\:border-b-primary,
.menu-item.active > .menu-toggle.menu-item-active\:border-b-primary,
.menu-item.active > .menu-toggle .menu-item-active\:border-b-primary {
  border-bottom-color: var(--as-primary);
}

.menu-item.active.menu-item-active\:font-medium,
.menu-item.active > .menu-link.menu-item-active\:font-medium,
.menu-item.active > .menu-link .menu-item-active\:font-medium,
.menu-item.active > .menu-toggle.menu-item-active\:font-medium,
.menu-item.active > .menu-toggle .menu-item-active\:font-medium {
  font-weight: 500;
}

.menu-item.active.menu-item-active\:text-primary,
.menu-item.active > .menu-link.menu-item-active\:text-primary,
.menu-item.active > .menu-toggle.menu-item-active\:text-primary,
.menu-item.active > .menu-toggle .menu-item-active\:text-primary {
  color: var(--as-primary);
}

.menu-item.here.menu-item-here\:border-b-primary,
.menu-item.here > .menu-link.menu-item-here\:border-b-primary,
.menu-item.here > .menu-link .menu-item-here\:border-b-primary,
.menu-item.here > .menu-toggle.menu-item-here\:border-b-primary,
.menu-item.here > .menu-toggle .menu-item-here\:border-b-primary {
  border-bottom-color: var(--as-primary);
}

.menu-item.here.menu-item-here\:font-medium,
.menu-item.here > .menu-link.menu-item-here\:font-medium,
.menu-item.here > .menu-link .menu-item-here\:font-medium,
.menu-item.here > .menu-toggle.menu-item-here\:font-medium,
.menu-item.here > .menu-toggle .menu-item-here\:font-medium {
  font-weight: 500;
}

.menu-item.here.menu-item-here\:text-primary,
.menu-item.here > .menu-link.menu-item-here\:text-primary,
.menu-item.here > .menu-toggle.menu-item-here\:text-primary,
.menu-item.here > .menu-toggle .menu-item-here\:text-primary {
  color: var(--as-primary);
}

.menu-item.here > .menu-link .menu-item-here\:text-primary {
  color: var(--as-primary);
  font-weight: 500 !important;
}

.menu-item.show.menu-item-show\:text-primary,
.menu-item.show > .menu-link.menu-item-show\:text-primary,
.menu-item.show > .menu-link .menu-item-show\:text-primary,
.menu-item.show > .menu-toggle.menu-item-show\:text-primary,
.menu-item.show > .menu-toggle .menu-item-show\:text-primary {
  color: var(--as-primary);
}

.menu-link:hover.menu-link-hover\:text-primary,
.menu-link:hover .menu-link-hover\:text-primary {
  color: var(--as-primary);
}

/* The same idea keyed on a data attribute instead of a class — KTDropdown,
   KTTabs and the password-reveal toggle. */
[data-dropdown].open .dropdown-open\:text-primary,
[data-dropdown-content].open .dropdown-open\:text-primary,
[data-tab-toggle].active.tab-active\:text-primary,
[data-tab-toggle].active .tab-active\:text-primary {
  color: var(--as-primary);
}

[data-toggle-password].active.toggle-password-active\:block,
[data-toggle-password].active .toggle-password-active\:block {
  display: block !important;
}

[data-toggle-password].active.toggle-password-active\:hidden,
[data-toggle-password].active .toggle-password-active\:hidden {
  display: none !important;
}

/* The last surviving `light:` variant. `<html>` carries `.light` in the light
   theme (`theme-switcher.js` keeps it in sync), so this is the header's two
   dropdown panels taking a stronger border on that theme only. */
.light .light\:border-gray-300 {
  border-color: var(--as-gray-300);
}

/* --- The app's own overrides of the families above -------------------------
   Four rules that came from the app's own additions at the END of
   `styles.css`, not from the framework block. They are here because the port
   would otherwise have broken them: each beats a rule above at EQUAL
   specificity, on source order alone, and the rule it beats has now moved into
   a file that loads after `styles.css`. They sit at the end of section 6.0 so
   they keep winning against the port and keep losing to everything below.

   `.menu-title-nav` is the config-tab button's ink, which has to survive the
   `text-primary` beside it in the markup. `.objecttable > table.table-border`
   restores the table's top border against `.card-grid .card-body table`'s
   `border: 0` — both are (0,2,1), so nothing but order separated them. */
.menu-title-nav {
  color: var(--as-gray-700);
  font-weight: 500;
}

.menu-title-nav:hover {
  color: var(--as-primary);
  border-bottom: 2px solid var(--as-primary);
}

/* Keep the top border so the table draws a single continuous rounded border on
   all four sides (matching the bottom). Do not zero it. */
.objecttable > table.table-border {
  border-top: 1px solid var(--as-gray-200);
}

/* The phone compaction. The frozen sheet's copy of the second rule also listed
   `.select`, `.input` and `.input-sm`; those three were left behind
   deliberately, because `appslate.css` has set their padding since the
   form-control restyle and the compaction has not reached them since. Carrying
   them over would have switched a dead declaration on rather than moved a live
   one. */
@media (max-width: 639px) {
  .container-fluid {
    padding-left: 0.5rem;
    padding-right: 0.5rem;
  }

  .card-grid .card-body table th:first-child,
  .card-grid .card-body table td:first-child,
  .card-body {
    padding-left: 0.3rem;
    padding-right: 0.3rem;
    padding-top: 0.2rem;
    padding-bottom: 0.2rem;
  }
}

/* --------------------------------------------------------------------------
   The button family (design `ui/Button.jsx`)
   --------------------------------------------------------------------------
   The whole family, re-cut from the reference's four variants onto our
   existing vocabulary — ghost is `.btn-light.btn-clear`, secondary is
   `.btn-light` (and `.btn-secondary`, which markup still writes 19 times and
   which has always been the same intent). No class is renamed and none is
   added; the superseded `styles.css` rules are deleted in the same change.

   The size ladder is OURS (28/32/40px at 11/12/13px), not the reference's
   32/36/40 — the correspondence table in STYLE_GUIDE §5.3 is deliberate about
   not closing that gap. What changed against the legacy cut: the corner radius
   reads the design's `--as-radius`, states are colour transitions on the
   design's own tokens instead of Metronic glows, and `:focus` no longer
   borrows the hover fill — keyboard focus is the global `:focus-visible` ring,
   which is the reference's own behaviour.

   Variants that markup no longer carries were retired with their legacy rules
   rather than re-cut: `.btn-dark`, `.btn-lg`, `.btn-tabs` (superseded by
   `.view-switch`) and the icon-font `i` sizing rules (the sprite replaced
   `<i>` icons). `.pagination .btn`, `.btn-group` and `.input-group` keep their
   contextual overrides in `styles.css` — they are context, not skin, and the
   residual doc owns them. */
.btn {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  line-height: 1;
  border-radius: var(--as-radius);
  height: 2.5rem;
  padding-left: 1rem;
  padding-right: 1rem;
  gap: 0.375rem;
  border: 1px solid transparent;
  font-weight: 500;
  font-size: 0.8125rem;
  outline: none;
  transition: color 0.15s ease, background-color 0.15s ease,
    border-color 0.15s ease;
}

.btn[disabled],
.btn.disabled {
  opacity: 0.5;
  pointer-events: none;
}

.btn-xs {
  height: 1.75rem;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
  font-size: 0.6875rem;
  gap: 0.25rem;
}

.btn-sm {
  height: 2rem;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
  font-size: 0.75rem;
  gap: 0.275rem;
}

/* AFTER the size ladder on purpose: `.btn-sm` sets side padding at the same
   specificity, and an icon square with 12px of padding clips its glyph — the
   legacy sheet declared `.btn-icon` twice to win this exact tie. */
.btn-icon {
  justify-content: center;
  flex-shrink: 0;
  padding: 0;
  gap: 0;
  width: 2.5rem;
}

.btn-xs.btn-icon {
  width: 1.75rem;
}

.btn-sm.btn-icon {
  width: 2rem;
}

/* Solid fills: the semantic token at rest, its `-hover` step on interaction,
   and the reference's resting shadow-sm. `.active` is in every state selector
   because scripts pin it (favourites, toolbar toggles); `:focus` is not,
   because a clicked button would otherwise hold its hover fill until blur. */
.btn-primary,
.btn-success,
.btn-warning,
.btn-danger,
.btn-info {
  box-shadow: 0 1px 2px rgb(15 23 42 / 5%);
}

.btn-primary {
  color: var(--as-primary-inverse);
  background-color: var(--as-primary);
}

.btn-primary:hover,
.btn-primary:active,
.btn-primary.active {
  background-color: var(--as-primary-hover);
}

.btn-success {
  color: #ffffff;
  background-color: var(--as-success);
}

.btn-success:hover,
.btn-success:active,
.btn-success.active {
  background-color: var(--as-success-hover);
}

.btn-warning {
  color: #ffffff;
  background-color: var(--as-warning);
}

.btn-warning:hover,
.btn-warning:active,
.btn-warning.active {
  background-color: var(--as-warning-hover);
}

.btn-danger {
  color: var(--as-danger-inverse);
  background-color: var(--as-danger);
}

.btn-danger:hover,
.btn-danger:active,
.btn-danger.active {
  background-color: var(--as-danger-hover);
}

/* The fill is `--as-info-filled` (violet-600), not the `--as-info` accent:
   white label text on the accent is 4.23:1, under AA for text this size
   (owner decision 2026-08-07 — the token-map row has the numbers). The hover
   steps to `--as-info-filled-hover` (violet-700) — rest and hover were both
   violet-600 once the legacy glow went, which left the state without a cue. */
.btn-info {
  color: var(--as-info-inverse);
  background-color: var(--as-info-filled);
}

.btn-info:hover,
.btn-info:active,
.btn-info.active {
  background-color: var(--as-info-filled-hover);
}

/* The design's "secondary": the elevated surface, body text, hairline border,
   hover to the muted surface. `.btn-secondary` has always been this intent
   under a Metronic spelling and takes the identical cut. */
.btn-light,
.btn-secondary {
  color: var(--as-text);
  border-color: var(--as-border);
  background-color: var(--as-bg-elevated);
  box-shadow: none;
}

.btn-light:hover,
.btn-light:active,
.btn-light.active,
.btn-secondary:hover,
.btn-secondary:active,
.btn-secondary.active {
  border-color: var(--as-border);
  background-color: var(--as-bg-muted);
  color: var(--as-text);
}

/* The one coloured `.btn-light` compound the legacy sheet had, kept: a delete
   action on a light button still reads as danger — a tinted rest that fills
   solid on hover, exactly the outline-danger shape. Three sites write it,
   one of them the list toolbar's Bulk Delete on every object list. */
.btn-light.btn-danger {
  color: var(--as-danger);
  background-color: color-mix(in srgb, var(--as-danger) 7%, var(--as-bg-elevated));
  border-color: var(--as-danger-muted);
  box-shadow: none;
}

.btn-light.btn-danger:hover,
.btn-light.btn-danger:active,
.btn-light.btn-danger.active {
  color: var(--as-danger-inverse);
  background-color: var(--as-danger);
  border-color: var(--as-danger);
  box-shadow: none;
}

/* The design's "ghost": transparent until hovered, secondary ink firming to
   body ink over the muted surface. This is the toolbar's icon-button idiom —
   77 sites write it. */
.btn-light.btn-clear {
  border-color: transparent;
  background-color: transparent;
  color: var(--as-text-secondary);
}

.btn-light.btn-clear:hover,
.btn-light.btn-clear:active,
.btn-light.btn-clear.active {
  background-color: var(--as-bg-muted);
  border-color: transparent;
  box-shadow: none;
  color: var(--as-text);
}

/* Coloured outline / clear / subtle compounds. State semantics are the legacy
   family's own — outline rests on a pale tint, clear and subtle rest
   transparent (one shared rule: their resting boxes are identical), outline
   and clear fill solid on interaction (one shared rule again), subtle hovers
   to the tint — with every colour read from the semantic tokens. The tint is
   mixed on the elevated surface so it stays a tint on the dark themes. Info's
   solid state is the AA `-filled` violet, same as the solid variant. */
.btn-outline.btn-primary {
  color: var(--as-primary);
  background-color: color-mix(in srgb, var(--as-primary) 7%, var(--as-bg-elevated));
  border-color: var(--as-primary-muted);
  box-shadow: none;
}

.btn-clear.btn-primary,
.btn-subtle.btn-primary {
  border-color: transparent;
  background-color: transparent;
  color: var(--as-primary);
  box-shadow: none;
}

.btn-outline.btn-primary:hover,
.btn-outline.btn-primary:active,
.btn-outline.btn-primary.active,
.btn-clear.btn-primary:hover,
.btn-clear.btn-primary:active,
.btn-clear.btn-primary.active {
  color: var(--as-primary-inverse);
  background-color: var(--as-primary);
  border-color: var(--as-primary);
  box-shadow: none;
}

.btn-subtle.btn-primary:hover,
.btn-subtle.btn-primary:active,
.btn-subtle.btn-primary.active {
  color: var(--as-primary);
  background-color: color-mix(in srgb, var(--as-primary) 7%, var(--as-bg-elevated));
  border-color: transparent;
  box-shadow: none;
}

.btn-outline.btn-success {
  color: var(--as-success);
  background-color: color-mix(in srgb, var(--as-success) 7%, var(--as-bg-elevated));
  border-color: var(--as-success-muted);
  box-shadow: none;
}

.btn-clear.btn-success,
.btn-subtle.btn-success {
  border-color: transparent;
  background-color: transparent;
  color: var(--as-success);
  box-shadow: none;
}

.btn-outline.btn-success:hover,
.btn-outline.btn-success:active,
.btn-outline.btn-success.active,
.btn-clear.btn-success:hover,
.btn-clear.btn-success:active,
.btn-clear.btn-success.active {
  color: #ffffff;
  background-color: var(--as-success);
  border-color: var(--as-success);
  box-shadow: none;
}

.btn-subtle.btn-success:hover,
.btn-subtle.btn-success:active,
.btn-subtle.btn-success.active {
  color: var(--as-success);
  background-color: color-mix(in srgb, var(--as-success) 7%, var(--as-bg-elevated));
  border-color: transparent;
  box-shadow: none;
}

.btn-outline.btn-warning {
  color: var(--as-warning);
  background-color: color-mix(in srgb, var(--as-warning) 7%, var(--as-bg-elevated));
  border-color: var(--as-warning-muted);
  box-shadow: none;
}

.btn-clear.btn-warning,
.btn-subtle.btn-warning {
  border-color: transparent;
  background-color: transparent;
  color: var(--as-warning);
  box-shadow: none;
}

.btn-outline.btn-warning:hover,
.btn-outline.btn-warning:active,
.btn-outline.btn-warning.active,
.btn-clear.btn-warning:hover,
.btn-clear.btn-warning:active,
.btn-clear.btn-warning.active {
  color: #ffffff;
  background-color: var(--as-warning);
  border-color: var(--as-warning);
  box-shadow: none;
}

.btn-subtle.btn-warning:hover,
.btn-subtle.btn-warning:active,
.btn-subtle.btn-warning.active {
  color: var(--as-warning);
  background-color: color-mix(in srgb, var(--as-warning) 7%, var(--as-bg-elevated));
  border-color: transparent;
  box-shadow: none;
}

.btn-outline.btn-danger {
  color: var(--as-danger);
  background-color: color-mix(in srgb, var(--as-danger) 7%, var(--as-bg-elevated));
  border-color: var(--as-danger-muted);
  box-shadow: none;
}

.btn-clear.btn-danger,
.btn-subtle.btn-danger {
  border-color: transparent;
  background-color: transparent;
  color: var(--as-danger);
  box-shadow: none;
}

.btn-outline.btn-danger:hover,
.btn-outline.btn-danger:active,
.btn-outline.btn-danger.active,
.btn-clear.btn-danger:hover,
.btn-clear.btn-danger:active,
.btn-clear.btn-danger.active {
  color: var(--as-danger-inverse);
  background-color: var(--as-danger);
  border-color: var(--as-danger);
  box-shadow: none;
}

.btn-subtle.btn-danger:hover,
.btn-subtle.btn-danger:active,
.btn-subtle.btn-danger.active {
  color: var(--as-danger);
  background-color: color-mix(in srgb, var(--as-danger) 7%, var(--as-bg-elevated));
  border-color: transparent;
  box-shadow: none;
}

.btn-outline.btn-info {
  color: var(--as-info);
  background-color: color-mix(in srgb, var(--as-info) 7%, var(--as-bg-elevated));
  border-color: var(--as-info-muted);
  box-shadow: none;
}

.btn-clear.btn-info,
.btn-subtle.btn-info {
  border-color: transparent;
  background-color: transparent;
  color: var(--as-info);
  box-shadow: none;
}

.btn-outline.btn-info:hover,
.btn-outline.btn-info:active,
.btn-outline.btn-info.active,
.btn-clear.btn-info:hover,
.btn-clear.btn-info:active,
.btn-clear.btn-info.active {
  color: var(--as-info-inverse);
  background-color: var(--as-info-filled);
  border-color: var(--as-info-filled);
  box-shadow: none;
}

.btn-subtle.btn-info:hover,
.btn-subtle.btn-info:active,
.btn-subtle.btn-info.active {
  color: var(--as-info);
  background-color: color-mix(in srgb, var(--as-info) 7%, var(--as-bg-elevated));
  border-color: transparent;
  box-shadow: none;
}

/* The dashed inline-link button — two live sites. Height and padding come from
   `.btn`; this variant only undoes the box. */
.btn-link {
  color: var(--as-primary);
  font-size: 0.8125rem;
  height: auto;
  padding-bottom: 0.25em;
  padding-left: 0;
  padding-right: 0;
  border-radius: 0;
  background-color: transparent;
  border-bottom: 1px dashed var(--as-primary);
}

.btn-link:hover,
.btn-link:active,
.btn-link.active {
  background-color: transparent;
  border-bottom: 1px dashed var(--as-primary-hover);
  color: var(--as-primary-hover);
}

.btn-link.btn-sm {
  font-size: 0.75rem;
}

/* `.btn-group`'s pressed state, re-skinned with the group's structural rules
   left in `styles.css` — the trimmed inner radii are geometry, not skin. */
.btn-group .btn.active {
  background-color: var(--as-bg-muted);
  border: 1px solid var(--as-border-strong);
  box-shadow: none;
}

/* --------------------------------------------------------------------------
   Form controls (design `ui/Input.jsx`, `Textarea.jsx`, `Checkbox.jsx`,
   `ui/select/*`)
   --------------------------------------------------------------------------
   `UI::form_html` owns every class name here and emits them unchanged — this
   is declarations only. The box: the design's input surface and border
   (`--as-input-bg` / `--as-input-border`), the `--as-radius` corner, muted
   placeholder ink. Sizes and the wrapper mechanics (`.input:not(input)` is a
   flex shell around a bare inner `<input>`) are the legacy family's own.

   Focus is the reference's: the border takes the primary and a 2px
   `--as-primary-muted` ring sits under it — a NEW declaration against the
   `--as-*` tokens, not a revival of the dead `--tw-input-focus-box-shadow`
   names (those reads were removed with `KNOWN_DANGLING`, 2026-08-07). Because
   the ring + border ARE the focus indicator, these controls opt out of the
   global `:focus-visible` outline — text fields match `:focus-visible` even
   on mouse focus, so without the opt-out every click would stack both.

   The `.\!input` important-twins were not re-cut: no markup has carried the
   `!input` spelling since the sprite-era cleanups, so the twins died with the
   legacy block. */
.input,
.textarea,
.select {
  box-shadow: none;
  outline: none;
  font-weight: 500;
  font-size: 0.8125rem;
  background-color: var(--as-input-bg);
  border: 1px solid var(--as-input-border);
  border-radius: var(--as-radius);
  color: var(--as-text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.input {
  display: block;
  width: 100%;
  appearance: none;
  height: 2.5rem;
  line-height: 2.125rem;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
}

.input::placeholder,
.input input::placeholder,
.textarea::placeholder,
.select::placeholder {
  color: var(--as-text-muted);
}

.input:hover,
.textarea:hover,
.select:hover {
  border-color: var(--as-border-strong);
}

.input:focus,
.input:has(input:focus),
.textarea:focus,
.select:focus {
  border-color: var(--as-primary);
  box-shadow: 0 0 0 2px var(--as-primary-muted);
}

.input:focus-visible,
.input:has(input:focus-visible),
.textarea:focus-visible,
.select:focus-visible {
  outline: none;
}

.input:disabled,
.input:has(input:disabled),
.textarea[disabled],
.textarea[readonly],
.select:disabled {
  background-color: var(--as-bg-muted);
  color: var(--as-text-muted);
}

.input-xs {
  height: 1.5rem;
  padding-left: 0.5rem;
  padding-right: 0.5rem;
}

.input-sm {
  font-size: 0.75rem;
  height: 2rem;
  padding-left: 0.625rem;
  padding-right: 0.625rem;
  line-height: 1.75rem;
}

/* The flex shell an icon-adjacent input renders as (search fields, the icon
   picker): the box stays the shell's, the inner `<input>` is transparent. */
.input:not(input) {
  display: flex;
  gap: 0.375rem;
  align-items: center;
  appearance: none;
  color: var(--as-text-secondary);
}

.input:not(input) input {
  flex-grow: 1;
  background-color: transparent;
  border-color: transparent;
  color: inherit;
  outline: none;
  font-size: inherit;
  font-weight: inherit;
}

.textarea {
  display: block;
  width: 100%;
  appearance: none;
  padding: 0.55rem 0.75rem;
}

/* The native select keeps its own chevron artwork; `--as-text-muted`'s middle
   gray reads on every theme, so one drawing serves all three. The arrow gutter
   (`padding-right`) no longer needs its legacy `!important`: this file wins
   the source-order tie against every `styles.css` utility, and the generated
   layer is `@layer`ed below both. */
.select {
  display: block;
  width: 100%;
  appearance: none;
  height: 2.5rem;
  line-height: 1;
  padding-left: 0.75rem;
  padding-right: 1.75rem;
  overflow: hidden;
  background-repeat: no-repeat;
  background-position: right 0.675rem center;
  background-size: 14px 11px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%2394a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3E%3C/svg%3E");
}

.select:-moz-focusring {
  color: transparent;
  text-shadow: none;
}

.select[multiple],
.select[size]:not([size='1']) {
  padding-right: 1rem;
  background-image: none;
  height: auto;
  overflow: auto;
}

.select[multiple] option:first-child {
  padding-top: 1.5em;
}

select[multiple].select option:checked {
  background: var(--as-primary) linear-gradient(0deg, var(--as-primary) 0%, var(--as-primary) 100%);
  color: #ffffff;
}

/* No `padding-right` here on purpose: the base `.select`'s 1.75rem chevron
   gutter must survive — the legacy sheet spelled 0.625rem and relied on the
   base rule's `!important` to overrule itself, so the gutter is what always
   rendered. */
.select-sm {
  font-size: 0.75rem;
  height: 2rem;
  padding-left: 0.625rem;
  background-size: 14px 10px;
  background-position: right 0.55rem center;
}

/* Labels and helper text. */
.form-label {
  display: flex;
  width: 100%;
  color: var(--as-text);
  font-weight: 500;
  font-size: 0.8125rem;
  line-height: 1.125rem;
  align-self: center;
}

.form-info {
  color: var(--as-text);
  font-weight: 500;
  font-size: 0.8125rem;
  line-height: 1.125rem;
}

.form-hint {
  color: var(--as-text-secondary);
  font-weight: 500;
  font-size: 0.75rem;
  line-height: 1rem;
}

/* Checkbox and radio: the legacy 22px targets stay (the reference's 16px box
   is exactly what the responsive pass's coarse-pointer floor exists to avoid);
   the skin is the input family's, checked is the primary fill, and the focus
   ring is keyboard-only — these are click targets, not text fields, so mouse
   focus stays quiet. The checkmark / dot / dash artwork is the legacy family's
   own (white strokes, which is right on the primary fill in every theme). */
.checkbox[type='checkbox'],
.radio[type='radio'] {
  flex-shrink: 0;
  appearance: none;
  box-shadow: none;
  height: 1.375rem;
  width: 1.375rem;
  background-color: var(--as-input-bg);
  border: 1px solid var(--as-input-border);
  background-repeat: no-repeat;
  background-position: center;
  transition: border-color 0.15s ease, background-color 0.15s ease,
    box-shadow 0.15s ease;
}

.checkbox[type='checkbox'] {
  border-radius: 0.375rem;
  background-size: auto;
}

.radio[type='radio'] {
  border-radius: 50%;
  background-size: contain;
}

.checkbox[type='checkbox']:checked,
.checkbox[type='checkbox'][aria-checked='true'],
.checkbox[type='checkbox']:indeterminate,
.radio[type='radio']:checked,
.radio[type='radio'][aria-checked='true'] {
  background-color: var(--as-primary);
  border-color: var(--as-primary);
}

.checkbox[type='checkbox']:checked,
.checkbox[type='checkbox'][aria-checked='true'] {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMicgaGVpZ2h0PSc5JyB2aWV3Qm94PScwIDAgMTIgOScgZmlsbD0nbm9uZSc+PHBhdGggZD0nTTEwLjM2NjcgMC41NDE2NDNMNC44MDAwNyA2LjEwODMxTDEuNTY2NzQgMi44NzQ5OEMxLjQxMDYxIDIuNzE5NzcgMS4xOTk0IDIuNjMyNjUgMC45NzkyNDEgMi42MzI2NUMwLjc1OTA4NiAyLjYzMjY1IDAuNTQ3ODc2IDIuNzE5NzcgMC4zOTE3NDEgMi44NzQ5OEMwLjIzNjUzMiAzLjAzMTExIDAuMTQ5NDE0IDMuMjQyMzIgMC4xNDk0MTQgMy40NjI0OEMwLjE0OTQxNCAzLjY4MjYzIDAuMjM2NTMyIDMuODkzODQgMC4zOTE3NDEgNC4wNDk5OEw0LjIxNjc0IDcuODc0OThDNC4zNzI4OCA4LjAzMDE5IDQuNTg0MDkgOC4xMTczIDQuODA0MjQgOC4xMTczQzUuMDI0NCA4LjExNzMgNS4yMzU2MSA4LjAzMDE5IDUuMzkxNzQgNy44NzQ5OEwxMS41NDE3IDEuNzI0OThDMTEuNjE5OCAxLjY0NzUxIDExLjY4MTggMS41NTUzNCAxMS43MjQxIDEuNDUzNzlDMTEuNzY2NSAxLjM1MjI0IDExLjc4ODIgMS4yNDMzMiAxMS43ODgyIDEuMTMzMzFDMTEuNzg4MiAxLjAyMzMgMTEuNzY2NSAwLjkxNDM3OSAxMS43MjQxIDAuODEyODNDMTEuNjgxOCAwLjcxMTI4MSAxMS42MTk4IDAuNjE5MTEzIDExLjU0MTcgMC41NDE2NDNDMTEuMzg1NiAwLjM4NjQzNCAxMS4xNzQ0IDAuMjk5MzE2IDEwLjk1NDIgMC4yOTkzMTZDMTAuNzM0MSAwLjI5OTMxNiAxMC41MjI5IDAuMzg2NDM0IDEwLjM2NjcgMC41NDE2NDNaJyBmaWxsPSd3aGl0ZScvPjwvc3ZnPg==");
}

.checkbox[type='checkbox']:indeterminate {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyMCAyMCc+PHBhdGggZmlsbD0nbm9uZScgc3Ryb2tlPSd3aGl0ZScgc3Ryb2tlLWxpbmVjYXA9J3JvdW5kJyBzdHJva2UtbGluZWpvaW49J3JvdW5kJyBzdHJva2Utd2lkdGg9JzMnIGQ9J002IDEwaDgnLz48L3N2Zz4=");
}

.radio[type='radio']:checked,
.radio[type='radio'][aria-checked='true'] {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyMicgaGVpZ2h0PScyMicgdmlld0JveD0nMCAwIDIyIDIyJyBmaWxsPSdub25lJz48Y2lyY2xlIGN4PScxMC45OTk1JyBjeT0nMTEnIHI9JzUuODY2NjcnIGZpbGw9J3doaXRlJy8+PC9zdmc+");
}

.checkbox[type='checkbox']:focus-visible,
.radio[type='radio']:focus-visible {
  outline: none;
  border-color: var(--as-primary);
  box-shadow: 0 0 0 2px var(--as-primary-muted);
}

.checkbox[type='checkbox']:disabled,
.radio[type='radio']:disabled {
  cursor: not-allowed;
  background-color: var(--as-bg-muted);
  border-color: var(--as-input-border);
}

.checkbox[type='checkbox']:disabled:checked,
.checkbox[type='checkbox']:disabled[aria-checked='true'],
.checkbox[type='checkbox']:disabled:indeterminate,
.radio[type='radio']:disabled:checked,
.radio[type='radio']:disabled[aria-checked='true'] {
  background-color: var(--as-primary-muted);
  border-color: transparent;
}

.checkbox-sm[type='checkbox'] {
  border-radius: 0.25rem;
  height: 1.125rem;
  width: 1.125rem;
}

.checkbox-sm[type='checkbox']:checked,
.checkbox-sm[type='checkbox'][aria-checked='true'] {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PSc3JyB2aWV3Qm94PScwIDAgMTAgNycgZmlsbD0nbm9uZSc+PHBhdGggZD0nTTguNDkzMiAwLjIzMzI5TDQuMDM5ODYgNC42ODY2MkwxLjQ1MzIgMi4wOTk5NkMxLjMyODI5IDEuOTc1NzkgMS4xNTkzMiAxLjkwNjEgMC45ODMxOTggMS45MDYxQzAuODA3MDc0IDEuOTA2MSAwLjYzODEwNiAxLjk3NTc5IDAuNTEzMTk4IDIuMDk5OTZDMC4zODkwMyAyLjIyNDg3IDAuMzE5MzM2IDIuMzkzODMgMC4zMTkzMzYgMi41Njk5NkMwLjMxOTMzNiAyLjc0NjA4IDAuMzg5MDMgMi45MTUwNSAwLjUxMzE5OCAzLjAzOTk2TDMuNTczMiA2LjA5OTk2QzMuNjk4MTEgNi4yMjQxMiAzLjg2NzA3IDYuMjkzODIgNC4wNDMyIDYuMjkzODJDNC4yMTkzMiA2LjI5MzgyIDQuMzg4MjkgNi4yMjQxMiA0LjUxMzIgNi4wOTk5Nkw5LjQzMzIgMS4xNzk5NkM5LjQ5NTY4IDEuMTE3OTggOS41NDUyOCAxLjA0NDI1IDkuNTc5MTIgMC45NjMwMDhDOS42MTI5NyAwLjg4MTc2OCA5LjYzMDQgMC43OTQ2MzIgOS42MzA0IDAuNzA2NjI0QzkuNjMwNCAwLjYxODYxNiA5LjYxMjk3IDAuNTMxNDc5IDkuNTc5MTIgMC40NTAyNEM5LjU0NTI4IDAuMzY5IDkuNDk1NjggMC4yOTUyNjYgOS40MzMyIDAuMjMzMjlDOS4zMDgyOSAwLjEwOTEyMyA5LjEzOTMyIDAuMDM5NDI4NyA4Ljk2MzIgMC4wMzk0Mjg3QzguNzg3MDcgMC4wMzk0Mjg3IDguNjE4MTEgMC4xMDkxMjMgOC40OTMyIDAuMjMzMjlaJyBmaWxsPSd3aGl0ZScvPjwvc3ZnPg==");
}

.checkbox-sm[type='checkbox']:indeterminate {
  background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMCcgaGVpZ2h0PScyJyB2aWV3Qm94PScwIDAgMTAgMicgZmlsbD0nbm9uZSc+PHBhdGggZD0nTTkuMDQ2MzggMC4yOTMzMzVMMS4wMzk3MSAwLjMzMzMzNUMwLjg2MjkwMyAwLjMzMzMzNSAwLjY5MzMzMyAwLjQwMzU3MyAwLjU2ODMwOSAwLjUyODU5OEMwLjQ0MzI4NSAwLjY1MzYyMiAwLjM3MzA0NyAwLjgyMzE5MSAwLjM3MzA0NyAxQzAuMzczMDQ3IDEuMTc2ODEgMC40NDMyODUgMS4zNDYzOCAwLjU2ODMwOSAxLjQ3MTQxQzAuNjkzMzMzIDEuNTk2NDMgMC44NjI5MDMgMS42NjY2NyAxLjAzOTcxIDEuNjY2NjdMOS4wMzk3MSAxLjYyNjY3QzkuMjE2NTMgMS42MjY2NyA5LjM4NjEgMS41NTY0MyA5LjUxMTEyIDEuNDMxNDFDOS42MzYxNCAxLjMwNjM4IDkuNzA2MzggMS4xMzY4MSA5LjcwNjM4IDAuOTYwMDAyQzkuNzA2MzggMC43ODMxOTEgOS42MzYxNCAwLjYxMzYyMiA5LjUxMTEyIDAuNDg4NTk3QzkuMzg2MSAwLjM2MzU3MyA5LjIxNjUzIDAuMjkzMzM1IDkuMDM5NzEgMC4yOTMzMzVIOS4wNDYzOFonIGZpbGw9J3doaXRlJy8+PC9zdmc+");
}

.checkbox-group,
.radio-group {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.checkbox-label {
  color: var(--as-text);
  font-weight: 500;
  font-size: 0.8125rem;
  line-height: 1.125rem;
}

/* The toggle switch. The track rests on the strong border gray and fills with
   the primary; the thumb is the elevated surface at rest and white on the
   fill. Keyboard focus takes the family's ring on the track. */
.switch {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  cursor: pointer;
}

.switch input[type='checkbox'] {
  display: flex;
  appearance: none;
  background-color: var(--as-border-strong);
  position: relative;
  cursor: pointer;
  flex-shrink: 0;
  height: 1.375rem;
  width: 2.125rem;
  border-radius: 1.375rem;
  transition: background-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.switch input[type='checkbox']::before {
  display: flex;
  position: absolute;
  content: '';
  height: 1rem;
  width: 1rem;
  border-radius: 100%;
  background-color: var(--as-bg-elevated);
  left: 0.25rem;
  top: 50%;
  transform: translateY(-50%);
  filter: drop-shadow(0 3px 4px rgb(0 0 0 / 3%));
  transition: all 0.15s ease-in-out;
}

.switch input[type='checkbox']:checked,
.switch input[type='checkbox'][aria-checked='true'] {
  background-color: var(--as-primary);
}

.switch input[type='checkbox']:checked::before,
.switch input[type='checkbox'][aria-checked='true']::before {
  background-color: #ffffff;
  left: calc(100% - 0.25rem);
  transform: translate(-100%, -50%);
  filter: none;
}

.switch input[type='checkbox']:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--as-primary-muted);
}

.switch input[type='checkbox']:disabled {
  background-color: var(--as-bg-muted);
  border: 1px solid var(--as-input-border);
  cursor: not-allowed;
  opacity: 0.5;
}

.switch input[type='checkbox']:disabled::before {
  background-color: var(--as-input-border);
}

.switch input[type='checkbox']:disabled:checked,
.switch input[type='checkbox']:disabled[aria-checked='true'] {
  background-color: var(--as-primary-muted);
  border: 0;
}

.switch input[type='checkbox']:disabled:checked::before,
.switch input[type='checkbox']:disabled[aria-checked='true']::before {
  background-color: var(--as-bg-elevated);
}

.switch-sm input[type='checkbox'] {
  height: 1.125rem;
  width: 1.875rem;
  border-radius: 1.125rem;
}

.switch-sm input[type='checkbox']::before {
  height: 0.75rem;
  width: 0.75rem;
}

/* --------------------------------------------------------------------------
   Dropdown and menu skins (design `ui/dropdown-menu/*`)
   --------------------------------------------------------------------------
   KTMenu and KTDropdown keep every data hook and behaviour; this is the panel
   and row skin only. The panel is the design's: elevated surface, hairline
   border, the `--as-shadow` float, `--as-radius` corners. Rows are the
   design's 12px ink on an 8px radius, hovering to the muted surface; the
   active / open / current row takes the primary-muted tint with primary ink
   (the treatment the reference's settings nav uses). Accordion indents and
   the `menu-item-active:*` stateful utilities stay in `styles.css` — they are
   geometry and markup-driven variants, not skin. */
.menu-dropdown {
  gap: 0.125rem;
  border: 1px solid var(--as-border);
  box-shadow: var(--as-shadow);
  background-color: var(--as-bg-elevated);
  color: var(--as-text);
  border-radius: var(--as-radius);
}

.menu-default {
  padding-top: 0.375rem;
  padding-bottom: 0.375rem;
}

.menu-default .menu-link {
  margin-left: 0.375rem;
  margin-right: 0.375rem;
  padding: 0.4375rem 0.625rem;
  border-radius: 8px;
}

.menu-default .menu-title {
  font-size: 0.75rem;
  line-height: 1.125rem;
  font-weight: 500;
}

.menu-default .menu-icon {
  margin-right: 0.625rem;
}

.menu-default .menu-separator {
  border-bottom: 1px solid var(--as-border);
  margin-top: 0.375rem;
  margin-bottom: 0.375rem;
}

/* Row ink is set on the LINK and inherited — the sprite icons draw with
   `currentColor`, so the icon follows its label in every state without a rule
   naming it. That is the reference's own behaviour and the D-7 repair's
   intent (the legacy block still aimed at `<i>` icon fonts nothing has
   rendered since the sprite landed). */
.menu-default .menu-item > .menu-link:hover {
  background-color: var(--as-bg-muted);
}

.menu-default .menu-item.active > .menu-link,
.menu-default .menu-item.here > .menu-link,
.menu-default .menu-item.show > .menu-link,
.menu-default .menu-item.focus > .menu-link {
  color: var(--as-primary);
}

.menu-default .menu-item.active > .menu-link,
.menu-default .menu-item.here > .menu-link {
  background-color: var(--as-primary-muted);
}

.menu-default .menu-item.disabled > .menu-link {
  opacity: 0.5;
}

.dropdown {
  display: flex;
}

.dropdown-content {
  display: none;
  align-items: stretch;
  flex-direction: column;
  border: 1px solid var(--as-border);
  box-shadow: var(--as-shadow);
  background-color: var(--as-bg-elevated);
  border-radius: var(--as-radius);
}

.open.dropdown > .dropdown-content,
.dropdown-content.open[data-popper-placement] {
  will-change: transform;
}

/* --------------------------------------------------------------------------
   Toasts (design `ui/toast/*`)
   --------------------------------------------------------------------------
   The DOM contract is `appslate.js`'s and does not change: one
   `.toast-container` stack, one `.toast.toast--<type>` per message,
   `.toast--visible` added a frame later. The card is the reference's shell —
   the semantic colour mixed into the elevated surface (12%) with a stronger
   mix on the border (35%), body ink, the `--as-shadow` float. The mixes are
   computed on the semantic token so all three themes derive their own tint,
   exactly like the reference's `color-mix` shells. `toast.test.js` pins this
   family HERE — it is the one test allowed to read the stylesheet as text. */
.toast-container {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 10000;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  max-width: min(400px, calc(100vw - 40px));
  pointer-events: none;
}

.toast {
  pointer-events: auto;
  max-width: 100%;
  padding: 12px 20px;
  border-radius: var(--as-radius);
  border: 1px solid transparent;
  box-shadow: var(--as-shadow);
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--as-text);
  overflow-wrap: break-word;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
}

.toast--visible {
  opacity: 1;
  transform: translateY(0);
}

.toast--success {
  background-color: color-mix(in srgb, var(--as-success) 12%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-success) 35%, transparent);
}

.toast--error {
  background-color: color-mix(in srgb, var(--as-danger) 12%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-danger) 35%, transparent);
}

.toast--warning {
  background-color: color-mix(in srgb, var(--as-warning) 14%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-warning) 40%, transparent);
}

.toast--info {
  background-color: color-mix(in srgb, var(--as-info) 12%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-info) 35%, transparent);
}

/* --------------------------------------------------------------------------
   The header user chip (design `ui/Avatar.jsx`)
   --------------------------------------------------------------------------
   The Avatar treatment on the one place the app draws a person: a filled
   circle of initials (or the profile photo) with a 2px ring in the bar's own
   surface colour, so it reads as punched into the topbar the way the
   reference's stack does. The colour is the primary — there is no per-user
   hue in the database, and inventing one is feature work. Markup keeps the
   `menu-toggle` / `btn` hooks; this class only owns the drawing. */
.user-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  height: 2rem;
  width: 2rem;
  border-radius: 9999px;
  background-color: var(--as-primary);
  color: #ffffff;
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  box-shadow: 0 0 0 2px var(--as-topbar);
}

.user-chip img {
  height: 100%;
  width: 100%;
  border-radius: inherit;
  object-fit: cover;
}

/* --------------------------------------------------------------------------
   The dialog box (design `ui/dialog/index.jsx`, `settings/SettingsModal.jsx`)
   --------------------------------------------------------------------------
   KTModal keeps every hook; the box is the design's: the elevated surface
   behind a hairline border, `--as-radius-lg` corners, the `--as-shadow`
   float, and a half-black scrim with the reference's 2px blur. The
   fixed-header / scroll-body / fixed-footer scaffold is markup, carried by
   the `partials/modals/*` dialogs themselves (`shrink-0` bars around an
   `overflow-y-auto min-h-0` body) — a stylesheet cannot decide which child
   of an arbitrary dialog is the scroll region.

   The skin itself now lives on the `.modal-content` / `.modal-backdrop` rules
   in section 6.0: the position, margin and flex mechanics it used to sit over
   arrived there with the pass (c) step 3 port, and two rules for one selector
   in one file is a trap, not a division of labour. */

/* Modal titles are `<h3>` everywhere they are rendered, so the display face
   and the tightening arrive from the base rules above; this is the size the
   reference gives its dialog title. */
.modal-title {
  font-size: 1.125rem;
  line-height: 1.75rem;
  font-weight: 600;
  color: var(--as-text);
}

/* Inline-edit fields inside object tables.
   `UI::form_html` emits them with no class at all (or only `.field-title`), so
   nothing in either stylesheet painted them and they took the browser's own
   field colours — a white box with black text, which reads fine on a white
   page and is a bright hole with invisible text on a dark one. The values
   below are the light theme's own (`--as-input-bg` IS white), so this changes
   nothing there.

   Selects are deliberately excluded: the `.status-*` / `.priority-*` classes
   carry meaning-bearing background colours and a rule this specific would
   silently win over them. */
.objecttable tbody td input:not([type='checkbox']):not([type='radio']),
.objecttable tbody td textarea {
  background-color: var(--as-input-bg);
  color: var(--as-text);
}

/* The dismiss control in a dialog's header.

   `btn-icon btn-light btn-clear` renders a borderless ghost square, so every
   view that wanted the boxed look wrote the box as an INLINE STYLE — a white
   background, a `#DBDFE9` border and a shadow, three literals no theme can
   follow and no stylesheet can outrank. On a dark theme it is a white chip on
   a dark dialog. This is the same box, read from the tokens; the markup drops
   the inline style and carries the class instead.

   Every live dismiss box is on it: the dashboard's dialogs, the workspace
   page's Add-Agent modal and the file browser's detail dialog since D-8, and
   the remaining copy-pasted sites since the D-16 sweep
   (`scripts/design_sweep_map.tsv`). The inline spelling survives only inside
   the deprecated pages, which are frozen as-is.

   `.btn` is in the selector because every site that carries this class also
   carries `btn-light btn-clear`, and `.btn-light.btn-clear` — in `styles.css`
   and re-cut above at D-15 — declares a transparent border and a transparent
   background at two class components. A single-class rule ties below both and
   loses, which is what this one did from the day it shipped: the box it
   describes never drew. This rule must also stay AFTER the D-15 button family:
   at the same two-class specificity, source order is what lets the box beat
   the ghost compound's transparent rest. */
.btn.modal-close {
  border: 1px solid var(--as-border);
  background-color: var(--as-bg-elevated);
  box-shadow: 0 3px 4px rgb(0 0 0 / 3%);
}

.btn.modal-close:hover {
  border-color: var(--as-border-strong);
  background-color: var(--as-bg-muted);
}

/* --- The icon tile -------------------------------------------------------- */

/* A rounded tile holding an icon, tinted with a colour the DATABASE chose —
   an agent's accent, a workflow's, a brand kit's primary, a service type's.
   Fourteen markup sites in thirteen views render one — five agents card
   grids, the chat switcher and its menu, the workflow chat header, four modal
   headers and the data cards — and eight runtime writers in six JS files add
   the rest, including the project page's pinned-tab badges and pin submenu.

   Every one of them used to write `style="background-color: {hex}1a; color:
   {hex}"` — the 8-digit-hex trick, which is a colour a reader has to decode
   and a value no rule can adjust. The hue now arrives as `--as-accent` and the
   tint is mixed here. Size, radius and icon size stay with the caller: they
   differ per surface and are not what was duplicated.

   The fallback is load-bearing. `color-mix` with an unparseable colour is an
   invalid value and the whole declaration is dropped, so a row storing
   something that is not a hex string would otherwise leave a bare chip. */
.icon-tile {
  background-color: color-mix(in srgb, var(--as-accent, var(--as-primary)) 12%, transparent);
  color: var(--as-accent, var(--as-primary));
}

/* `.hidden` sits at line 2,755 of `styles.css` and the component rules run from
   line 11,000 down, both unlayered and both a single class — so `display: flex`
   wins the tie on source order and an element carrying `hidden` is SHOWN. The
   generated `.hidden` cannot settle it either, being layered. Components whose
   `display` moved into THIS file (the button and form families) beat the frozen
   sheet's `.hidden` even harder — this file loads later — so their companions
   below stay required; `AppslateResidualTest` derives the set from both sheets.

   D-11 answered `.select` and guessed the rest were latent. They are not. Every
   single-class rule in the frozen sheet that declares a visible `display` after
   line 2,755 was cross-referenced against every `class` attribute in the corpus
   that also carries a hider, and five more are live in markup today, at 27
   sites: `.btn` (14 — dialog actions across eight views), `.objecttable` (5 —
   `listtable`, `patternlist`, `qclist`, `listfields`, `builderfields`, where
   `@if($i!=1) hidden @endif` means every table after the first one rendered),
   `.input` (4 — the query builder's second value field), `.card` (2) and
   `.form-label` (2).

   The compound is what makes this safe to write once. `.x.hidden` is two
   classes, so it also outranks a responsive `lg:flex` — which would be wrong if
   any of these elements combined a component class, `hidden`, and a variant
   that shows it again at a breakpoint. None does; `AppslateResidualTest` fails
   if one appears, because that site would need the media query instead.

   `.btn-hide` is not in this list and does not need to be: it is declared
   below the button family it has to beat, so it wins that tie on order. */
.btn.hidden,
.card.hidden,
.form-label.hidden,
.input.hidden,
.objecttable.hidden,
.select.hidden {
  display: none;
}

/* --- The count tile ------------------------------------------------------- */

/* `.stat-tile` / `-value` / `-label` lived here — a number and the thing it
   counts, in a row above a list. Its only markup was the file browser's, and it
   was removed with that page (docs/DEPRECATED_PAGES.md). Four views still
   hand-roll the identical block with `text-2xl font-bold` over
   `text-xs text-gray-500` — `trenddetail`, `ads`, and `intelligence` twice — so
   the phase that restyles one of them wants the component back; `git log` has
   it, and the dashboard's `.dash-metric` is the near neighbour, not a
   substitute (that one addresses author HTML by position). */

/* --- The segmented view switch -------------------------------------------- */

/* The reference's `ViewToggle`: a bordered track holding one button per view,
   the active one filled. Three call sites — the files page's list/cards/grouped
   toggle and the tasks page's two control groups (Gantt's Day/Week/Month,
   the calendar's Day/Week/Month/List) — which between them used to carry four
   different treatments of the same control, one of them `.fc-button`'s.

   The reference slides the fill between buttons with a shared-element
   animation; ours is a colour transition (plan §3 D-6 — motion is CSS, and a
   slide needs a per-index position no stylesheet can compute).

   `display` is declared here, so the `.hidden` companion is mandatory: this
   file is unlayered and the utility is not, and both control groups are toggled
   by class. Buttons are addressed as direct children rather than by a class of
   their own — every call site's markup already distinguishes them, and a
   `.view-switch-item` would be a class that says only "I am inside a
   view switch". */
.view-switch {
  display: inline-flex;
  max-width: 100%;
  overflow-x: auto;
  gap: 0.125rem;
  padding: 0.25rem;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  scrollbar-width: none;
}

.view-switch.hidden {
  display: none;
}

.view-switch::-webkit-scrollbar {
  display: none;
}

.view-switch > button {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  min-width: 2rem;
  height: 1.75rem;
  padding: 0 0.5rem;
  border: 0;
  border-radius: 8px;
  background-color: transparent;
  color: var(--as-text-secondary);
  font-size: 0.8125rem;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.view-switch > button:hover {
  background-color: var(--as-bg-muted);
  color: var(--as-text);
}

.view-switch > button.active,
.view-switch > button.active:hover {
  background-color: var(--as-primary);
  color: var(--as-primary-inverse);
}

@media (prefers-reduced-motion: reduce) {
  .view-switch > button {
    transition: none;
  }
}

/* --- The card grid's two per-card controls ------------------------------- */

/* Seven grids render the same kebab menu and the same favourite star — the
   dashboard's value cards plus the six agents card lists — and every one of
   them wrote the whole look as utilities in its own markup: `bg-white
   rounded-lg shadow-lg border border-gray-200` on the menu, `text-gray-700
   hover:bg-gray-100` on each row, `text-yellow-500` / `text-gray-400` toggled
   by hand on the star. Seven copies of one component, all of them a white slab
   with invisible labels on the dark themes.

   `window.AppSlateCardGrid` already binds these by class, so the class was
   always the contract; this makes it carry the look as well. The positioning
   (`absolute right-0 mt-1 w-44 z-[9999]`) stays in the markup — each grid
   anchors its menu differently. */
.value-card-menu {
  min-width: 9rem;
  padding: 0.25rem;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow);
}

.value-card-menu-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.4375rem 0.625rem;
  border: 0;
  border-radius: 0.5rem;
  background-color: transparent;
  color: var(--as-text-secondary);
  font-size: 0.875rem;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.value-card-menu-item:hover {
  background-color: var(--as-bg-muted);
  color: var(--as-text);
}

.value-card-menu-item-danger {
  color: var(--as-danger);
}

.value-card-menu-item-danger:hover {
  background-color: var(--as-danger-muted);
  color: var(--as-danger);
}

/* The `⋯` trigger. `.btn` would bring a border and a min-width this does not
   want, so the square is declared here rather than reused. Same 2rem box as
   the star beside it — the reference gives both controls `h-8 w-8`, and two
   different sizes in one control cluster reads as a mistake. */
.value-card-menu-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 0.5rem;
  background-color: transparent;
  color: var(--as-text-muted);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.value-card-menu-btn:hover {
  background-color: var(--as-bg-muted);
  color: var(--as-text);
}

/* The star. The state is read from `aria-pressed`, which the markup and
   `setFavoriteUi` both already set — one source of truth instead of an ARIA
   attribute and a pair of palette classes that had to be kept in step.
   The gold is the reference's own literal and is deliberately not a token: it
   means "favourite" on every theme, the way a warning colour does. */
.card-favorite-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex-shrink: 0;
  padding: 0;
  border: 0;
  border-radius: 0.5rem;
  background-color: transparent;
  color: var(--as-text-muted);
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.card-favorite-btn:hover {
  background-color: var(--as-bg-muted);
  color: #ffd700;
}

.card-favorite-btn[aria-pressed='true'] {
  color: #ffd700;
}

/* --- Status, priority and trend pills ------------------------------------- */

/* One vocabulary, built from a response-set option's own text:
   `UI::form_html` slugs the selected option into `.status-<slug>` /
   `.priority-<slug>`, `manager.js` re-writes it when the value changes, and a
   free-standing `<span>` or `<div>` carrying the class gets the same pill. No
   id is involved anywhere — a new option produces a class name on its own, and
   renders unstyled if no rule below matches it.

   The four agent states arrived at D-9 already token-tinted; the eight rules
   under them are the older family, and until this phase they were literal light
   chips in `styles.css` (`#fef2f2`, `#fffbeb`, `#f0fdf4`, `#F8F5FF` …) — a
   bright hole punched through every dark-theme list, and the reason the tasks
   page could not pass its own three-theme check: a kanban card's priority and
   the status on a gantt row ARE these classes. The tints are the semantic
   family's now, so the same meaning is the same colour whether it is a pill, a
   kanban column or a chart series.

   Anatomy comes first and lists EVERY member, which is what makes an agent's
   pill and a task's the same object. `.priority-primary`, `.priority-none` and
   `.status-none` were in the colour rules but not in the anatomy, so a response
   set with a None or a Primary option rendered a tinted `<select>` beside its
   siblings' pills; `.hm-off`, which is the same "no value" case, was a pill all
   along. No option on this database slugs to any of the three, so adding them
   changes nothing here and settles what happens the day one does.

   The six heatmap chips (`.hm-active` … `.hm-success`) are full members too —
   `workflowstepstatus.colormap` stores these names and the workflow-status
   `<select>` on a list row carries one beside a `.select-status`, so they were
   the last literal light chips on the same tables the re-cut fixed. Each maps
   to the semantic family its meaning shares with the `.status-*` member of the
   same colour (active reads as success, exactly as `.status-active` does;
   inactive as the parked gray). One of them also reaches a Frappe Gantt bar as
   a `custom_class` — an SVG `<g>`, which has no CSS box, so the anatomy is
   inert there just as the legacy tint was. Both `!important`s are answers to a legacy
   `!important`, not to an ordinary rule — this file already wins every tie
   against `styles.css` by load order, so nothing else here needs one. */
.priority-critical,
.priority-severe,
.priority-high,
.priority-medium,
.priority-low,
.priority-primary,
.priority-none,
.status-none,
.status-not-started,
.status-submitted,
.status-in-progress,
.status-on-hold,
.status-deferred,
.status-delayed,
.status-escalated,
.status-completed,
.status-active,
.status-paused,
.status-draft,
.status-archived,
.trend-up,
.trend-down,
.trend-flat,
.trend-alt,
.hm-off,
.hm-active,
.hm-inactive,
.hm-critical,
.hm-warning,
.hm-info,
.hm-success {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  height: 1.75rem;
  /* `.select` reserves the dropdown arrow's gutter with
     `padding-right: 1.75rem !important`, and most members of this family land
     on a `<select>`. The pill suppresses that arrow (`background-image: none`),
     so the gutter is dead space that pushes a centred label off centre. */
  padding: 0.25rem 0.75rem !important;
  border: 0;
  border-radius: 0.5rem;
  background-image: none;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.25rem;
  text-align: center;
  text-transform: uppercase;
}

/* Critical is the one filled chip in the family — everything else is a tint of
   its meaning on the page surface. */
.priority-critical,
.priority-severe {
  background-color: var(--as-danger);
  color: var(--as-danger-inverse);
}

.priority-high,
.status-escalated,
.trend-up,
.hm-critical {
  background-color: var(--as-danger-muted);
  color: var(--as-danger);
}

.priority-medium,
.status-in-progress,
.status-paused,
.hm-warning {
  background-color: var(--as-warning-muted);
  color: var(--as-warning);
}

.priority-low,
.status-submitted,
.status-completed,
.status-active,
.trend-down,
.hm-success,
.hm-active {
  background-color: var(--as-success-muted);
  color: var(--as-success);
}

/* On hold, delayed and deferred are the app's "parked" states and have always
   shared the violet the reference gives to info. */
.status-on-hold,
.status-delayed,
.status-deferred,
.hm-info {
  background-color: var(--as-info-muted);
  color: var(--as-info);
}

.priority-primary,
.trend-alt {
  background-color: var(--as-primary-muted);
  color: var(--as-primary);
}

.status-not-started,
.status-draft,
.status-archived,
.trend-flat,
.hm-inactive {
  background-color: var(--as-bg-muted);
  color: var(--as-text-secondary);
}

/* The absence of a value, not a value. It sits a step back from
   "not started": the page surface rather than the recessed one. */
.priority-none,
.status-none,
.hm-off {
  background-color: var(--as-bg);
  color: var(--as-text-muted);
}

/* A cell holding one is centred on the pill, not on the column's text
   alignment. `.txl` / `.txc` / `.txr` are `!important` utilities a stored
   `htmlclass` can put on the same cell, and this has to outrank them.

   This list is the anatomy list, member for member. Nothing about a member
   decides whether it can reach a `<td>`: the class is built from the selected
   option's own text (`'status-' . $selected_text` in `UI::form_html`, and the
   same string in `manager.js`), so any member is one response-set option away
   from a list column. Four were missing here — the agent states, which today
   only ever render in a `<span>` on a card, so nothing was mis-drawn yet. */
td:has(.priority-critical),
td:has(.priority-severe),
td:has(.priority-high),
td:has(.priority-medium),
td:has(.priority-low),
td:has(.priority-primary),
td:has(.priority-none),
td:has(.status-none),
td:has(.status-not-started),
td:has(.status-submitted),
td:has(.status-in-progress),
td:has(.status-on-hold),
td:has(.status-deferred),
td:has(.status-delayed),
td:has(.status-escalated),
td:has(.status-completed),
td:has(.status-active),
td:has(.status-paused),
td:has(.status-draft),
td:has(.status-archived),
td:has(.trend-up),
td:has(.trend-down),
td:has(.trend-flat),
td:has(.trend-alt),
td:has(.hm-off),
td:has(.hm-active),
td:has(.hm-inactive),
td:has(.hm-critical),
td:has(.hm-warning),
td:has(.hm-info),
td:has(.hm-success) {
  text-align: center !important;
}

/* --------------------------------------------------------------------------
   The sprite glyph
   --------------------------------------------------------------------------
   `svg.icon` is the single most reached-for selector in the app: every sprite
   `<use>` in every layout goes through it. It clamps the glyph to the current
   font size so a `text-sm` / `size-5` utility beside it is what decides how
   big the icon draws, and it sets the stroke-not-fill contract the Hugeicons
   artwork is drawn for.

   The `:not(.hidden)` half is what lets `hidden` still hide an icon: without
   it the `display` here would tie with the utility and win on order.
   -------------------------------------------------------------------------- */
svg.icon {
  min-width: 1em;
  min-height: 1em;
  max-width: 1em;
  max-height: 1em;
  fill: none;
  stroke: currentColor;
  vertical-align: -0.125em;
}

svg.icon:not(.hidden) {
  display: inline-block;
}

/* --------------------------------------------------------------------------
   The sprite icon picker (`partials/_icon-picker.blade.php`)
   --------------------------------------------------------------------------
   Popover grid for choosing any symbol id in `public/assets/media/sprite.svg`.
   -------------------------------------------------------------------------- */
.icon-picker {
  position: relative;
  display: block;
  /* Fill the form-row flex slot so the trigger matches its `.input` / `.select`
     siblings rather than shrinking to the icon label's text width. */
  width: 100%;
}

.icon-picker-trigger {
  min-height: 2.5rem;
}

.icon-picker-preview svg {
  color: var(--as-text-secondary);
}

.icon-picker-dialog {
  position: absolute;
  top: calc(100% + 0.25rem);
  left: 0;
  right: 0;
  z-index: 60;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08), 0 4px 10px rgba(0, 0, 0, 0.04);
  max-width: 100%;
}

.icon-picker-dialog[hidden] {
  display: none;
}

.icon-picker-dialog-inner {
  display: flex;
  flex-direction: column;
  max-height: 360px;
}

.icon-picker-header {
  padding: 0.625rem 0.75rem;
  border-bottom: 1px solid var(--as-border);
}

.icon-picker-search {
  width: 100%;
}

.icon-picker-body {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0.25rem;
  padding: 0.625rem;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}

.icon-picker-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 1 / 1;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 0.375rem;
  background: var(--as-gray-50);
  color: var(--as-text-secondary);
  cursor: pointer;
  /* `svg.icon` clamps to 1em globally; bump the font size here so the sprite
     renders at its natural 24px design size instead of the 16px default. */
  font-size: 1.5rem;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}

.icon-picker-tile:hover,
.icon-picker-tile:focus-visible {
  background: var(--as-primary-muted);
  border-color: var(--as-primary);
  color: var(--as-primary);
  outline: none;
}

.icon-picker-tile.is-selected {
  background: var(--as-primary-muted);
  border-color: var(--as-primary);
  color: var(--as-primary);
  box-shadow: 0 0 0 2px var(--as-primary-muted);
}

.icon-picker-tile.is-hidden {
  display: none;
}

.icon-picker-empty {
  grid-column: 1 / -1;
  padding: 1.5rem;
  text-align: center;
  color: var(--as-gray-600);
  font-size: 0.875rem;
}

.icon-picker-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0.75rem;
  border-top: 1px solid var(--as-border);
}

/* --------------------------------------------------------------------------
   Colour chips and swatches
   --------------------------------------------------------------------------
   The accent "custom" chip is a `<label>` wrapping an invisible
   `<input type="color">` that sits full-size on top, so clicking the chip
   opens the OS colour wheel at the chip's exact position. Used in the New
   Agent modal alongside the preset chips, and reusable from any modal that
   needs a free-form hex.
   -------------------------------------------------------------------------- */
.ag-accent-chip.is-custom {
  background: conic-gradient(
    from 0deg,
    #ef4444, #f59e0b, #eab308, #22c55e,
    #06b6d4, #3b82f6, #8b5cf6, #ec4899, #ef4444
  );
  color: #ffffff;
  position: relative;
  overflow: hidden;
  cursor: pointer;
  padding: 0;
  display: inline-block;
}

.ag-accent-chip.is-custom::after {
  content: "";
  position: absolute;
  inset: 33%;
  border-radius: 9999px;
  background: #ffffff;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
  pointer-events: none;
}

.ag-accent-chip.is-custom.is-selected::after {
  background: var(--ag-custom-color, #ffffff);
  inset: 0;
}

.ag-accent-chip.is-custom.is-selected {
  box-shadow: 0 0 0 2px var(--as-bg-elevated), 0 0 0 4px var(--ag-custom-color, var(--as-primary));
}

/* Selected-state ring for any `.ag-accent-chip`, preset or custom. Scoped
   copies exist per-modal for historical reasons; this global rule is what lets
   the `UI::form_html` swatch variant light up without the caller needing a
   modal-specific override. */
.ag-accent-chip.is-selected {
  transform: scale(1.15);
  box-shadow: 0 0 0 2px var(--as-bg-elevated), 0 0 0 4px currentColor;
}

/* The invisible full-size colour input that catches the click on the custom
   chip's label. */
.ag-accent-native-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  opacity: 0;
  cursor: pointer;
}

/* Native `<input type="color">` as a rounded swatch — what the generic
   `UI::form_html` renderer emits for `ResponseType='color'`. */
.color-picker-swatch {
  -webkit-appearance: none;
  appearance: none;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid var(--as-input-border);
  border-radius: 0.5rem;
  background: transparent;
  cursor: pointer;
}

.color-picker-swatch::-webkit-color-swatch-wrapper { padding: 0; }
.color-picker-swatch::-webkit-color-swatch { border: none; border-radius: 0.375rem; }
.color-picker-swatch::-moz-color-swatch { border: none; border-radius: 0.375rem; }
.color-picker-swatch:disabled { opacity: 0.5; cursor: not-allowed; }

/* --- The tag input --------------------------------------------------------
   `partials/components/tag-input.blade.php`: a scrolling strip of removable
   bubbles with a text field after them, used both standalone and inside a
   table cell.

   The view carried twenty-nine `!important`s here with a note saying they were
   fighting `.td-link`, whose `span` rule stretches every span in the cell to
   fill it. They were not: `.tag-input-wrapper .tag-bubble .tag-remove-x`
   already outranks `.td-link span` two classes to one, and nothing else in any
   stylesheet matches these elements. All twenty-nine came off with the computed
   styles byte-identical (`scripts/css-cascade-probe.cjs tag-input`). What they
   were really written against was `styles.css`, which C-9 deleted.
   -------------------------------------------------------------------------- */
.tag-input-wrapper {
  position: relative;
  display: block;
  width: 100%;
}

/* The strip. It scrolls sideways rather than wrapping, so one row of a table
   cannot become three because a record has six tags. */
.tag-input-wrapper .tag-display-area {
  position: relative;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 0.25rem;
  min-height: 38px;
  max-height: 38px;
  padding: 0.25rem 0.5rem;
  overflow-x: auto;
  overflow-y: hidden;
  background-color: var(--as-input-bg);
  border: 1px solid var(--as-gray-200);
  border-radius: 0.25rem;
  cursor: text;
  scrollbar-width: thin;
  scrollbar-color: var(--as-gray-300) transparent;
}

/* In a table the strip is capped so one busy row cannot widen the column. */
.table .tag-display-area {
  max-width: 150px;
}

.tag-input-wrapper .tag-display-area::-webkit-scrollbar {
  height: 4px;
}

.tag-input-wrapper .tag-display-area::-webkit-scrollbar-track {
  background: transparent;
}

.tag-input-wrapper .tag-display-area::-webkit-scrollbar-thumb {
  background-color: var(--as-gray-300);
  border-radius: 2px;
}

.tag-input-wrapper .tag-bubble,
.tag-input-wrapper .tag-input-field {
  position: relative;
}

.tag-input-wrapper .tag-bubble {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  height: 28px;
  padding: 0.125rem 0.5rem;
  background-color: var(--as-gray-100);
  border: 1px solid var(--as-gray-300);
  border-radius: 0.25rem;
  white-space: nowrap;
  cursor: pointer;
  transition: all 0.2s;
}

/* A read-only field's bubbles cannot be removed, so they get neither the
   pointer nor the hover treatment below. The modifier replaces a substring
   test against an inline style attribute the markup no longer carries — and
   which matched every bubble anyway, so the delete affordance was appearing
   on read-only tags too. */
.tag-input-wrapper .tag-bubble--readonly {
  cursor: default;
}

.tag-input-wrapper .tag-input-field {
  flex: 0 0 80px;
  width: 80px;
  height: 28px;
  padding: 0.25rem;
  background: transparent;
  border: 0;
  outline: none;
  box-shadow: none;
}

/* The remove control rides the bubble's top-right corner and appears while a
   removable bubble is hovered. */
.tag-input-wrapper .tag-bubble .tag-remove-x {
  position: absolute;
  top: -4px;
  right: -4px;
  left: auto;
  display: none;
  align-items: center;
  justify-content: center;
  width: 12px;
  min-width: 12px;
  height: 12px;
  min-height: 12px;
  margin: 0;
  padding: 0 0 1px;
  background-color: var(--as-danger);
  border-radius: 50%;
  color: var(--as-danger-inverse);
  font-size: 10px;
  font-weight: bold;
  line-height: 9px;
  text-align: center;
}

.tag-bubble:not(.tag-bubble--readonly):hover {
  background-color: var(--as-danger-light);
  border-color: var(--as-danger);
}

.tag-bubble:not(.tag-bubble--readonly):hover .tag-remove-x {
  display: flex;
}

/* --------------------------------------------------------------------------
   The pill
   --------------------------------------------------------------------------
   A short label in a tinted, fully-rounded box: a status, a severity, a count
   of runs, a category. Ten of these grew independently — one per surface, each
   a few hundredths of a rem apart — because each was written inside the view
   that needed it and could not see the other nine. They are this family now.

   THREE sizes, and that is the whole ladder:
     · `--sm`   the dense one, for a label beside a title or inside a table row
     · (none)   the default, for a label that stands on its own
     · `--lg`   for a page-level readout

   TONE is separate from size, and comes two ways. A modifier class when the
   markup knows what it is (`.pill--danger`), or `data-status` when a value from
   the database decides (`data-status="failed"`) — the second is what the agent
   surfaces use, so a status string can go straight into the attribute without a
   PHP or JS lookup table. Both resolve to the same six tones.

   `.ctype-pill` is the one deliberate exception and it keeps its own twelve
   tints: those say what a file IS, and if they read the semantic tones then
   "this is a spreadsheet" and "this succeeded" would be the same green. It
   shares the SHAPE from here and nothing else.
   -------------------------------------------------------------------------- */
.pill,
.ctype-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  border: 1px solid transparent;
  border-radius: 9999px;
  white-space: nowrap;
  vertical-align: middle;
}

.pill {
  padding: 0.125rem 0.5rem;
  background-color: var(--as-gray-200);
  color: var(--as-gray-700);
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.5;
}

.pill--sm,
.ctype-pill {
  padding: 0.0625rem 0.4375rem;
  font-size: 0.6875rem;
}

.pill--lg {
  padding: 0.375rem 1rem;
  font-size: 0.875rem;
}

/* An icon inside a pill never dictates the pill's height. */
.pill .icon,
.ctype-pill .icon {
  width: 0.875rem;
  height: 0.875rem;
  flex-shrink: 0;
}

/* --- Tone ------------------------------------------------------------------
   Six, and the default (no modifier, no recognised status) is the neutral one.
   The `[data-status]` list is the same six tones reached by a database value
   instead of a class: `draft`, `canceled` and `skipped` are absent on purpose —
   they ARE the neutral default. (`[data-trigger]` had a set here too, inherited
   from the badge partial; nothing writes that attribute on a pill — it is a
   filter hook on the workflow CARD — so it went.) */
.pill--primary,
.pill[data-status='running'] {
  background-color: var(--as-primary-light);
  color: var(--as-primary);
}

.pill--success,
.pill[data-status='completed'],
.pill[data-status='active'] {
  background-color: var(--as-success-light);
  color: var(--as-success);
}

.pill--warning,
.pill[data-status='paused'],
.pill[data-status='disabled'] {
  background-color: var(--as-warning-light);
  color: var(--as-warning);
}

.pill--danger,
.pill[data-status='failed'],
.pill[data-status='error'],
.pill[data-status='archived'] {
  background-color: var(--as-danger-light);
  color: var(--as-danger);
}

/* The info tone has no class form: `pending` is the only thing that reaches
   for it, and a modifier nobody writes is a rule nobody maintains. */
.pill[data-status='pending'] {
  background-color: var(--as-info-light);
  color: var(--as-info);
}

.pill--neutral {
  background-color: var(--as-gray-200);
  color: var(--as-gray-700);
}

/* The one filled tone. A critical finding is the only thing in the app allowed
   to shout, so it inverts instead of tinting. */
.pill--critical {
  background-color: var(--as-danger);
  color: var(--as-danger-inverse);
}

/* Lets a caller paint the border from a colour the base cannot know — the risk
   matrix's rating bands are configurable data and arrive as an inline style. */
.pill--outline {
  border-color: currentcolor;
}

/* --------------------------------------------------------------------------
   Content-type pills (`content-type-pill.js`)
   --------------------------------------------------------------------------
   A tinted, rounded type badge of sprite icon + short label, shared by the
   agent file and artifact lists. The palette is deliberately separate from the
   ingestion-status badges (success/danger/warning) so "what type is this" and
   "is it ready" never read as the same colour signal — which is also why these
   twelve stay literal tints rather than joining the semantic tokens.

   The SHAPE comes from `.pill` above (which lists `.ctype-pill` in its base and
   `--sm` rules), so only what differs is here. `content-type-pill.js` writes
   these class names, and ten test files assert them; keeping the name is what
   lets the family be shared without touching any of that.
   -------------------------------------------------------------------------- */
.ctype-pill {
  font-weight: 500;
  line-height: 1rem;
}

.ctype-pill--compact { padding-left: 0.4rem; padding-right: 0.4rem; }
.ctype-pill--green  { background-color: #dcfce7; color: #166534; border-color: #bbf7d0; }
.ctype-pill--blue   { background-color: #dbeafe; color: #1e40af; border-color: #bfdbfe; }
.ctype-pill--red    { background-color: #fee2e2; color: #991b1b; border-color: #fecaca; }
.ctype-pill--amber  { background-color: #fef3c7; color: #92400e; border-color: #fde68a; }
.ctype-pill--purple { background-color: #f3e8ff; color: #6b21a8; border-color: #e9d5ff; }
.ctype-pill--rose   { background-color: #ffe4e6; color: #9f1239; border-color: #fecdd3; }
.ctype-pill--cyan   { background-color: #cffafe; color: #155e75; border-color: #a5f3fc; }
.ctype-pill--yellow { background-color: #fef9c3; color: #854d0e; border-color: #fde047; }
.ctype-pill--indigo { background-color: #e0e7ff; color: #3730a3; border-color: #c7d2fe; }
.ctype-pill--orange { background-color: #ffedd5; color: #c2410c; border-color: #fed7aa; }
.ctype-pill--slate  { background-color: #f1f5f9; color: #334155; border-color: #e2e8f0; }
/* `--info` mirrors the brand info purple, hardcoded as a light tint so it
   matches this family instead of adapting to the dark themes. */
.ctype-pill--info   { background-color: #f1edfd; color: #7239ea; border-color: #d8c9f9; }

/* --------------------------------------------------------------------------
   The file-type filter (`filetype-filter.js`, `_filetype-filter.blade.php`)
   --------------------------------------------------------------------------
   The closed control mirrors a `.select` — border, radius, chevron, 2.5rem
   height — but opens a checkbox popover instead of a native list. Selected
   types render as their `.ctype-pill` badges inside the closed control, which
   is why it lives beside that family.
   -------------------------------------------------------------------------- */
.ftype-filter { position: relative; display: inline-flex; flex: 0 0 auto; }

.ftype-filter-control {
  display: inline-flex; align-items: center; gap: 0.3rem;
  min-width: 8.5rem; max-width: 22rem; height: 2.5rem;
  padding: 0 0.625rem; background-color: var(--as-input-bg);
  border: 1px solid var(--as-input-border); border-radius: 0.375rem;
  color: var(--as-text-secondary); font-size: 0.8125rem; font-weight: 500;
  cursor: pointer; -webkit-appearance: none; appearance: none; overflow: hidden;
}

.ftype-filter-control:hover { border-color: var(--as-border-strong); }
.ftype-filter-control[aria-expanded="true"] { border-color: var(--as-primary); }

/* Active state — at least one type selected. A tinted border keeps the
   narrowed-list signal visible even when the badges scroll out of view. */
.ftype-filter.has-selection .ftype-filter-control { border-color: var(--as-primary); }

.ftype-filter-display {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center;
  gap: 0.25rem; overflow: hidden; flex-wrap: nowrap;
}

.ftype-filter-placeholder { color: var(--as-text-muted); white-space: nowrap; }
.ftype-filter-display .ftype-filter-pill { flex: 0 0 auto; }
.ftype-filter-caret { width: 0.75rem; height: 0.75rem; flex-shrink: 0; color: var(--as-text-muted); }

.ftype-filter-menu {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 50;
  min-width: 14rem; max-width: 18rem; max-height: 20rem;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--as-bg-elevated); border: 1px solid var(--as-border); border-radius: 0.5rem;
  box-shadow: 0 10px 24px rgba(15, 23, 42, 0.15);
}

/* The `display: flex` above would otherwise out-rank the global `.hidden`
   utility (same specificity, declared later), leaving the popover stuck open. */
.ftype-filter-menu.hidden { display: none; }

.ftype-filter-clear {
  display: flex; align-items: center; gap: 0.4rem;
  width: 100%; padding: 0.45rem 0.6rem; border: 0; background: transparent;
  text-align: left; cursor: pointer; color: var(--as-gray-600); font-size: 0.8125rem;
}

.ftype-filter-clear:hover { background: var(--as-bg-muted); color: var(--as-text); }
.ftype-filter-clear .icon { width: 0.85rem; height: 0.85rem; }
.ftype-filter-divider { height: 1px; background: var(--as-border); }
.ftype-filter-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0.25rem; }
.ftype-filter-empty { padding: 0.5rem 0.6rem; color: var(--as-text-muted); font-size: 0.8125rem; }

.ftype-filter-row {
  display: flex; align-items: center; gap: 0.5rem;
  width: 100%; padding: 0.35rem 0.5rem; border: 0; background: transparent;
  border-radius: 0.375rem; cursor: pointer; text-align: left;
}

.ftype-filter-row:hover { background: var(--as-bg-muted); }

.ftype-filter-check {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 1rem; height: 1rem; border: 1px solid var(--as-input-border);
  border-radius: 0.25rem; color: var(--as-primary-inverse); background: var(--as-input-bg);
}

.ftype-filter-check .icon { width: 0.7rem; height: 0.7rem; opacity: 0; }

.ftype-filter-row.is-checked .ftype-filter-check {
  background: var(--as-primary); border-color: var(--as-primary);
}

.ftype-filter-row.is-checked .ftype-filter-check .icon { opacity: 1; }

/* Keeps the search box and its filter on one line: the search grows, the
   filter sits to its right. Without this the `width: 100%` `.input` claims the
   whole flex-wrap row and the filter drops below. */
.ftype-search-group { display: flex; align-items: center; gap: 0.5rem; flex: 1 1 auto; min-width: 12rem; }
.ftype-search-group > .input { flex: 1 1 auto; width: auto; min-width: 0; }

/* --------------------------------------------------------------------------
   The project/workspace file row
   --------------------------------------------------------------------------
   A retryable ingestion badge (error / stalled) IS the retry control — a
   `<button>` wearing the `.badge` chrome. Hover or keyboard focus swaps the
   status label for a refresh glyph and "Retry"; the badge colour stays, so the
   state still reads at rest. Processing and chat-source rows stay a `<span>`.
   -------------------------------------------------------------------------- */
.project-file-status--retryable { cursor: pointer; font-family: inherit; -webkit-appearance: none; appearance: none; }
.project-file-status__retry { display: none; align-items: center; gap: 0.25em; }
.project-file-status__retry .icon { width: 0.85em; height: 0.85em; }

.project-file-status--retryable:hover .project-file-status__label,
.project-file-status--retryable:focus-visible .project-file-status__label { display: none; }

.project-file-status--retryable:hover .project-file-status__retry,
.project-file-status--retryable:focus-visible .project-file-status__retry { display: inline-flex; }

.project-file-menu { position: relative; display: inline-flex; }

.project-file-menu-dropdown {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 40;
  min-width: 12rem; padding: 0.25rem;
  background: var(--as-bg-elevated); border: 1px solid var(--as-border);
  border-radius: 0.5rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  display: flex; flex-direction: column;
}

.project-file-menu-dropdown[hidden] { display: none; }

.project-file-menu-item {
  display: flex; align-items: center; gap: 0.45rem;
  text-align: left; padding: 0.4rem 0.65rem; font-size: 0.8125rem;
  border-radius: 0.375rem; background: transparent; border: 0; cursor: pointer;
  color: var(--as-text); white-space: nowrap;
}

.project-file-menu-item .icon { width: 0.9em; height: 0.9em; flex: 0 0 auto; }
.project-file-menu-item:hover { background: var(--as-bg-muted); }
.project-file-menu-item--danger { color: var(--as-danger); }
.project-file-menu-item--danger:hover { background: var(--as-danger-muted); }
.project-data-badge { white-space: nowrap; }

/* --------------------------------------------------------------------------
   The object table
   --------------------------------------------------------------------------
   The list surface `objectdetails_*`, `listtable` and the pattern/QC builders
   all render. The `.card` family gives it its box; these rules give it the
   flex column that lets the table fill the page, the header treatment, and the
   two-line title/description cell the list uses in place of a second column.
   -------------------------------------------------------------------------- */
.objecttable,
.objecttable > form {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.objecttable > form > .detail-tab-content:not(.hidden) {
  display: flex;
  flex-direction: column;
  flex: 1;
}

.objecttable thead th {
  color: var(--as-text-secondary);
  text-transform: uppercase;
  background-color: var(--as-bg-muted);
  /* The table element itself draws the top border, so the header cell must NOT
     add its own — a second, inset border creates a white seam between two
     lines at the rounded corners. Bottom corners stay clean for the same
     reason (the last row's td has no bottom border). */
  border-top: 0;
  border-bottom: 1px solid var(--as-border);
  border-left: none;
  border-right: none;
}

/* The 1px border makes the table's inner corner radius 1px smaller than its
   outer 0.75rem. Round the header corner cells to that inner radius so the
   header background meets the border exactly — otherwise a 0.75rem cell corner
   recedes from the inner edge and leaves a thin sliver along the curve. (The
   sticky last cell escapes the table's overflow clip, so it relies on this
   radius rather than the clip.) */
.objecttable thead th:first-child {
  border-top-left-radius: calc(var(--as-radius) - 1px);
}

.objecttable thead th:last-child {
  border-top-right-radius: calc(var(--as-radius) - 1px);
}

.objecttable tbody td {
  color: var(--as-gray-800);
}

/* The first cell carries the row's checkbox and its identifying value, so it
   reads one ramp step stronger than the rest of the row. `listtable` and
   `datatable` each carried their own copy of this; it belongs with the family. */
.objecttable tbody td:nth-child(1) {
  color: var(--as-text);
}

.objecttable tbody td[data-appslate*="field_autoincrement"],
.objecttable tbody td[data-appslate*="field_autoincrement"] a,
.objecttable tbody td input.field-title {
  color: var(--as-text);
  font-weight: 500;
}

.objecttable tbody td:first-child:not(:has(input[type="checkbox"])) {
  padding-left: 1.25rem;
}

/* The description rides under its title cell rather than claiming a column of
   its own: the header is hidden, the title cell reserves the room, and the
   description is absolutely placed into it. */
.objecttable thead th[data-appslate*="field_description"] {
  display: none;
}

.objecttable tbody tr {
  position: relative;
}

.objecttable tbody td[data-appslate*="field_title"],
.objecttable tbody td[data-appslate*="field_name"] {
  position: relative;
  padding-bottom: 3rem;
}

.objecttable tbody td[data-appslate*="field_description"] {
  position: absolute;
  top: 2.5rem;
  left: 0;
  right: 0;
  padding: 0.5rem 1.25rem;
  background: transparent;
  border: none;
}

.objecttable tbody td[data-appslate*="field_description"] input,
.objecttable tbody td[data-appslate*="field_description"] textarea {
  width: 100%;
}

/* The scrolled header clone. It is taken out of flow, so every table-display
   property the browser inferred has to be restated. */
.objecttable thead.is-sticky,
thead.is-sticky {
  position: fixed;
  top: 64px;
  z-index: 25;
  background-color: var(--as-bg-muted);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  display: table;
  table-layout: fixed;
  border-collapse: collapse;
}

.objecttable thead.is-sticky tr,
thead.is-sticky tr {
  display: table-row;
}

.objecttable thead.is-sticky th,
thead.is-sticky th {
  display: table-cell;
  background-color: var(--as-bg-muted);
  padding-top: 0.625rem;
  padding-bottom: 0.625rem;
  color: var(--as-text-secondary);
  text-transform: uppercase;
  border-top: 1px solid var(--as-border);
  border-bottom: 1px solid var(--as-border);
  border-left: none;
  border-right: none;
  font-weight: 500;
  font-size: 0.8125rem;
  line-height: 1.75rem;
  vertical-align: middle;
}

/* A row menu inside a horizontal scroll container would be clipped by it, so
   the open menu escapes to the viewport and the container stops clipping. */
.scrollable-x-auto:has(.menu-dropdown[style*="display: block"]) {
  overflow: visible;
}

.scrollable-x-auto .menu-dropdown[style*="display: block"] {
  position: fixed;
  z-index: 10000;
}

/* The sticky last column casts a shadow only while there is something to
   scroll to, which `has-scroll-content` marks. */
th.sticky.right-0::before,
td.sticky.right-0::before {
  content: '';
  position: absolute;
  left: -10px;
  top: 0;
  bottom: 0;
  width: 10px;
  background: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.06));
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.scrollable-x-auto.has-scroll-content th.sticky.right-0::before,
.scrollable-x-auto.has-scroll-content td.sticky.right-0::before {
  opacity: 1;
}

/* The whole cell is the link's hit area, not just its text. */
.td-link {
  position: relative;
}

.td-link a,
.td-link button,
.td-link span,
.td-link .heatmap {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  text-align: left;
  display: flex;
  justify-content: left;
  align-items: center;
  padding-left: 0.25rem;
  white-space: nowrap;
}

.td-link .txr,
.td-link span.txr {
  justify-content: right;
  padding-left: 0;
  padding-right: 0.25rem;
}

.td-link .txc,
.td-link span.txc {
  justify-content: center;
  padding-left: 0;
  padding-right: 0;
}

/* A thumbnail in a table cell is capped to the row's height and loses the
   caption, size and action chrome the card view shows. */
td .file_image {
  height: 50px;
}

td .file_image img {
  max-height: 100%;
  max-width: 100%;
}

td .file_image .caption,
td .file_image .filesize,
td .file_image .file-actions {
  display: none;
}

table td .file_image {
  align-items: flex-start;
}

table td .file_image img {
  max-height: 60px;
  max-width: 60px;
}

table td .image-container {
  max-height: 60px;
  overflow: hidden;
}

/* The card-list row sizes. One surface, four densities. */
.lst-itm-1,
.lst-itm-lg,
.lst-itm-md,
.lst-itm-sm,
.lst-itm-xs {
  background: var(--as-bg-elevated);
  margin: 5px 0;
}

.lst-itm-1,
.lst-itm-lg { padding: 15px 20px; }
.lst-itm-md { padding: 6px 20px; }
.lst-itm-sm { padding: 4px 20px; }
.lst-itm-xs { padding: 2px 2px 2px 10px; margin: 2px 0; }

.lst-itm-1.active {
  background: var(--as-bg-muted);
}

/* --------------------------------------------------------------------------
   The detail tab strip
   --------------------------------------------------------------------------
   The object-detail pages' own tabs — distinct from `.tab` above, which is the
   framework strip. The nav sticks to the top of the scrolling pane.
   -------------------------------------------------------------------------- */
.detail-tabs-nav {
  background: var(--as-bg-elevated);
  position: sticky;
  top: 0;
  z-index: 5;
}

.detail-tab-btn {
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
  background: none;
  outline: none;
  white-space: nowrap;
}

.detail-tab-btn:hover {
  color: var(--as-text-secondary);
  border-color: var(--as-border-strong);
}

.detail-tab-btn.active {
  color: var(--as-primary);
  border-color: var(--as-primary);
}

.detail-tab-content.hidden {
  display: none;
}

/* Toolbar and list-table buttons (Assign, Submit, Share, Bulk Delete, workflow
   actions) start hidden and are toggled on row selection. `.btn` sets
   `display: inline-flex` at the same specificity, so this rule has to sit
   BELOW the button family to win — which is why it is here and not beside the
   `.x.hidden` companions above. Do NOT reuse `.hidden` here: the layouts
   depend on the "hidden lg:flex" responsive pattern, which only works while
   `.hidden` has normal specificity. */
.btn-hide {
  display: none;
}

/* --------------------------------------------------------------------------
   Collapsible groups
   --------------------------------------------------------------------------
   The object-detail field groups and the linked-record sections share one
   header/chevron/body anatomy; `.collapsed` on the group is what closes it.
   -------------------------------------------------------------------------- */
.category-group-header,
.linked-group-header {
  user-select: none;
  background-color: var(--as-gray-50);
  border-bottom: 1px solid var(--as-border);
}

.linked-group-header {
  border-radius: 0.25rem;
}

.category-group-header:hover,
.linked-group-header:hover {
  background-color: var(--as-bg-muted);
}

.category-group.collapsed .category-group-items,
.linked-group.collapsed .linked-group-items {
  display: none;
}

.category-group.collapsed {
  padding-top: 0.25rem;
  padding-bottom: 0.25rem;
}

.category-chevron {
  transition: transform 0.15s ease-in-out;
}

.category-group.collapsed .category-chevron,
.linked-group.collapsed .category-chevron {
  transform: rotate(-90deg);
}

.linked-group .checkbox[type="checkbox"] {
  height: 0.875rem;
  width: 0.875rem;
  border-radius: 0.188rem;
}

.linked-section-box {
  border: 1px solid var(--as-border);
  border-radius: 0.5rem;
  overflow: hidden;
  margin-top: 0.5rem;
}

.linked-section-header {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.375rem 0.75rem;
  cursor: pointer;
  user-select: none;
  transition: background-color 0.15s;
}

.linked-section-header:hover {
  background-color: var(--as-gray-50);
}

.linked-section-box .linked-chevron {
  font-size: 0.625rem;
  color: var(--as-text-muted);
  flex-shrink: 0;
}

.linked-section-box .linked-section-body {
  border-top: 1px solid var(--as-border);
  padding: 0.5rem 0.75rem;
}

.linked-section-box .ls-tab-btn {
  cursor: pointer;
  border: none;
  background: none;
  padding: 0.125rem 0.375rem;
  font-size: 0.6875rem;
  font-weight: 400;
  color: var(--as-gray-600);
  border-radius: 0.25rem;
  white-space: nowrap;
  transition: color 0.15s, background-color 0.15s;
}

.linked-section-box .ls-tab-btn:hover {
  color: var(--as-text-secondary);
  background-color: var(--as-bg-muted);
}

.linked-section-box.open .ls-tab-btn.active {
  color: var(--as-primary);
  background-color: transparent;
}

/* --------------------------------------------------------------------------
   Small shared pieces
   -------------------------------------------------------------------------- */

/* A JS-owned visibility flag, distinct from `.hidden`: the code that toggles
   it never has to reason about which component rule it is fighting. */
.is-hidden {
  display: none;
}

/* The page builder's element picker. Three layouts (`master`, `masterajax`
   and `app`) each carried a byte-identical `addSelectionStyles()` that built
   this rule set as a string and injected it into `<head>` on first use; all
   three now share this one copy. The `!important`s stay: the overlay has to
   beat whatever the element it is picking already wears. */
.selection-mode-active * {
  cursor: crosshair !important;
}

.builder-hover-outline {
  outline: 2px solid var(--as-primary) !important;
  outline-offset: 2px !important;
  border-radius: 4px !important;
  background-color: color-mix(in srgb, var(--as-primary) 5%, transparent) !important;
  transition: all 0.15s ease-in-out !important;
}

.builder-element-selected {
  position: relative !important;
  z-index: 1000 !important;
  outline: 4px solid var(--as-primary) !important;
  outline-offset: 4px !important;
  border-radius: 6px !important;
  background-color: color-mix(in srgb, var(--as-primary) 10%, transparent) !important;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--as-primary) 30%, transparent) !important;
}

.builder-element-selected::after {
  content: '';
  position: absolute;
  top: -6px;
  right: -6px;
  width: 16px;
  height: 16px;
  background-color: var(--as-primary);
  border: 2px solid var(--as-bg-elevated);
  border-radius: 50%;
  pointer-events: none;
}

/* Cancels the card border for a panel that supplies its own frame. */
.no-border .card-border,
.no-border .card-body {
  border-width: 0;
  border: 0;
  border-color: transparent;
}

/* A heatmap cell's own colour is what should read, so the select inside it
   draws no chrome of its own. */
.heatmap .select {
  border: 0;
  background-color: transparent;
}

/* Marks a field the assistant filled in. */
.ai-border,
.ai-border:hover {
  border: 1px solid var(--as-info);
}

#ai-assistant-btn.active {
  background-color: var(--as-info-filled);
  border-color: var(--as-info-filled);
  color: var(--as-info-inverse);
}

#ai-assistant-btn.active:hover {
  background-color: var(--as-info-filled-hover);
  border-color: var(--as-info-filled-hover);
}

.spinner {
  border: 0.2rem solid var(--as-bg-elevated);
  border-top: 0.2rem solid var(--as-primary);
  border-radius: 50%;
  width: 1rem;
  height: 1rem;
  animation: as-spin 2s linear infinite;
}

@keyframes as-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* --------------------------------------------------------------------------
   Vendor surfaces
   -------------------------------------------------------------------------- */

/* TinyMCE — the editor's own chrome, which the app only ever adjusts. The
   promotion banner and the status bar's right half are upsells. */
.tox-tinymce {
  margin-top: 1rem;
  margin-bottom: 1rem;
}

/* Forced because the adversary is TinyMCE's own skin stylesheet, which the
   editor injects at runtime — nothing here can be ordered ahead of it. */
.tox-promotion,
.tox-statusbar__right-container {
  display: none !important;
}

/* D3 graph marks (the pattern and QC node views). */
.network-node {
  stroke-width: 1.5;
  stroke: var(--as-bg-elevated);
  fill-opacity: 0.8;
}

.network-node:hover {
  fill-opacity: 1;
  stroke-width: 2;
}

/* The D3 hover tooltip. Theme-constant on purpose: it is drawn over a chart
   canvas whose own colours do not follow the theme. */
.chart-tooltip {
  position: absolute;
  opacity: 0;
  pointer-events: none;
  background-color: rgba(40, 40, 40, 0.9);
  color: #f0f0f0;
  border: 1px solid #222;
  border-radius: 6px;
  padding: 10px;
  font-size: 12px;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
  transition: opacity 0.2s ease-in-out;
  max-width: 300px;
  z-index: 1070;
}

/* --- The notice -------------------------------------------------------------
   The inline sibling of the toast: a server-rendered flash or callout that
   sits in the page flow instead of floating over it. Same shell as `.toast`
   — the semantic token mixed into the elevated surface (12% fill / 35%
   border), body ink — so a saved-settings flash and a saved-settings toast
   read as one family. Replaces the Bootstrap `alert alert-*` markup a few
   pages carried, which no stylesheet has ever served. There is no dismiss
   control: the Bootstrap ones wired `data-bs-dismiss` to a library that never
   shipped, and a notice that dies with the next navigation needs no button. */
.notice {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  padding: 0.75rem 1rem;
  border-radius: var(--as-radius);
  border: 1px solid transparent;
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--as-text);
  background-color: var(--as-bg-muted);
}

.notice > .icon {
  flex-shrink: 0;
  margin-top: 0.125rem;
}

.notice-success {
  background-color: color-mix(in srgb, var(--as-success) 12%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-success) 35%, transparent);
}

.notice-danger {
  background-color: color-mix(in srgb, var(--as-danger) 12%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-danger) 35%, transparent);
}

.notice-warning {
  background-color: color-mix(in srgb, var(--as-warning) 12%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-warning) 35%, transparent);
}

.notice-info {
  background-color: color-mix(in srgb, var(--as-info) 12%, var(--as-bg-elevated));
  border-color: color-mix(in srgb, var(--as-info) 35%, transparent);
}

/* --- TinyMCE chrome on the dark themes --------------------------------------
   The editor loads its own oxide skin at runtime (skin.min.css, injected after
   this file), whose chrome is hardcoded light — on both dark themes the
   toolbar, bespoke selects, sidebar and editor header rendered as opaque light
   surfaces (the report builder's four theme-audit findings). These rules
   re-point the CHROME onto the design surfaces; the CONTENT iframe stays
   paper deliberately — a document being authored is a light canvas, the same
   exemption the artifact renderers carry. Scoped to the dark themes so light
   keeps oxide's own look pixel-for-pixel; the `html[data-theme-mode]` prefix
   also outranks every skin selector, which is what lets a stylesheet that
   loads FIRST beat one injected later without a single !important. Floating
   parts (menus, dialogs) render under `.tox-tinymce-aux` on <body>, still
   inside the html[data-theme-mode] subtree, so they follow too. */

html[data-theme-mode='semi-dark'] .tox-tinymce,
html[data-theme-mode='dark'] .tox-tinymce {
  border-color: var(--as-border);
}

html[data-theme-mode='semi-dark'] .tox:not(.tox-tinymce-inline) .tox-editor-header,
html[data-theme-mode='dark'] .tox:not(.tox-tinymce-inline) .tox-editor-header {
  background-color: var(--as-bg-elevated);
}

html[data-theme-mode='semi-dark'] .tox .tox-toolbar-overlord,
html[data-theme-mode='semi-dark'] .tox .tox-toolbar,
html[data-theme-mode='semi-dark'] .tox .tox-toolbar__overflow,
html[data-theme-mode='semi-dark'] .tox .tox-toolbar__primary,
html[data-theme-mode='dark'] .tox .tox-toolbar-overlord,
html[data-theme-mode='dark'] .tox .tox-toolbar,
html[data-theme-mode='dark'] .tox .tox-toolbar__overflow,
html[data-theme-mode='dark'] .tox .tox-toolbar__primary {
  background-color: var(--as-bg-elevated);
  /* the skin draws its row dividers as a repeating gradient; keep the
     mechanic, re-ink the line */
  background-image: repeating-linear-gradient(var(--as-border) 0 1px, transparent 1px 39px);
}

html[data-theme-mode='semi-dark'] .tox .tox-tbtn,
html[data-theme-mode='dark'] .tox .tox-tbtn {
  background: transparent;
  color: var(--as-text-secondary);
}

html[data-theme-mode='semi-dark'] .tox .tox-tbtn svg,
html[data-theme-mode='dark'] .tox .tox-tbtn svg {
  fill: currentColor;
}

html[data-theme-mode='semi-dark'] .tox .tox-tbtn:hover,
html[data-theme-mode='semi-dark'] .tox .tox-tbtn:focus,
html[data-theme-mode='dark'] .tox .tox-tbtn:hover,
html[data-theme-mode='dark'] .tox .tox-tbtn:focus {
  background: var(--as-bg-muted);
  color: var(--as-text);
}

html[data-theme-mode='semi-dark'] .tox .tox-tbtn--enabled,
html[data-theme-mode='semi-dark'] .tox .tox-tbtn--enabled:hover,
html[data-theme-mode='dark'] .tox .tox-tbtn--enabled,
html[data-theme-mode='dark'] .tox .tox-tbtn--enabled:hover {
  background: var(--as-primary-muted);
  color: var(--as-text);
}

html[data-theme-mode='semi-dark'] .tox .tox-tbtn--disabled,
html[data-theme-mode='semi-dark'] .tox .tox-tbtn:disabled,
html[data-theme-mode='dark'] .tox .tox-tbtn--disabled,
html[data-theme-mode='dark'] .tox .tox-tbtn:disabled {
  color: var(--as-text-muted);
}

html[data-theme-mode='semi-dark'] .tox .tox-tbtn--bespoke,
html[data-theme-mode='dark'] .tox .tox-tbtn--bespoke {
  background: var(--as-bg-muted);
}

html[data-theme-mode='semi-dark'] .tox .tox-sidebar,
html[data-theme-mode='dark'] .tox .tox-sidebar {
  --tox-private-sidebar-background-color: var(--as-bg-elevated);
}

/* The floating collections — toolbar menus, the blocks/font dropdowns. */
html[data-theme-mode='semi-dark'] .tox .tox-menu,
html[data-theme-mode='dark'] .tox .tox-menu {
  background-color: var(--as-bg-elevated);
  border-color: var(--as-border);
}

html[data-theme-mode='semi-dark'] .tox .tox-collection__item,
html[data-theme-mode='dark'] .tox .tox-collection__item {
  color: var(--as-text);
}

html[data-theme-mode='semi-dark'] .tox .tox-collection--list .tox-collection__item--active,
html[data-theme-mode='dark'] .tox .tox-collection--list .tox-collection__item--active {
  background-color: var(--as-bg-muted);
}

html[data-theme-mode='semi-dark'] .tox .tox-collection--list .tox-collection__item--enabled,
html[data-theme-mode='dark'] .tox .tox-collection--list .tox-collection__item--enabled {
  background-color: var(--as-primary-muted);
  color: var(--as-text);
}

/* The editor's own dialogs (table properties, cell properties…): the same
   surfaces-and-ink treatment, fields on the input tokens. */
html[data-theme-mode='semi-dark'] .tox .tox-dialog,
html[data-theme-mode='semi-dark'] .tox .tox-dialog__header,
html[data-theme-mode='semi-dark'] .tox .tox-dialog__footer,
html[data-theme-mode='dark'] .tox .tox-dialog,
html[data-theme-mode='dark'] .tox .tox-dialog__header,
html[data-theme-mode='dark'] .tox .tox-dialog__footer {
  background-color: var(--as-bg-elevated);
  color: var(--as-text);
}

html[data-theme-mode='semi-dark'] .tox .tox-dialog__title,
html[data-theme-mode='dark'] .tox .tox-dialog__title {
  color: var(--as-text);
}

html[data-theme-mode='semi-dark'] .tox .tox-label,
html[data-theme-mode='semi-dark'] .tox .tox-toolbar-label,
html[data-theme-mode='dark'] .tox .tox-label,
html[data-theme-mode='dark'] .tox .tox-toolbar-label {
  color: var(--as-text-secondary);
}

html[data-theme-mode='semi-dark'] .tox .tox-textfield,
html[data-theme-mode='semi-dark'] .tox .tox-textarea,
html[data-theme-mode='semi-dark'] .tox .tox-listbox--select,
html[data-theme-mode='semi-dark'] .tox .tox-selectfield select,
html[data-theme-mode='dark'] .tox .tox-textfield,
html[data-theme-mode='dark'] .tox .tox-textarea,
html[data-theme-mode='dark'] .tox .tox-listbox--select,
html[data-theme-mode='dark'] .tox .tox-selectfield select {
  background-color: var(--as-input-bg);
  border-color: var(--as-input-border);
  color: var(--as-text);
}

html[data-theme-mode='semi-dark'] .tox .tox-button--secondary,
html[data-theme-mode='dark'] .tox .tox-button--secondary {
  background-color: var(--as-bg-muted);
  border-color: var(--as-border);
  color: var(--as-text);
}

/* ==========================================================================
   7. Utility exceptions
   ==========================================================================
   Deliberately near-empty: the generated Tailwind layer supplies the utility
   vocabulary. Only a utility that layer cannot express belongs here. */

/* --- `bg-transparent`, at a rank the layer cannot give it -------------------
   The one utility the markup uses to STRIP a component's own surface: the
   search modal's field is `.input` with its chrome removed, and two
   intelligence panels are `.panel-left` with the card background off. Both
   pair the utility with the component class, so a generated `bg-transparent`
   loses — it ships inside `@layer utilities`, and every component rule in this
   file is unlayered.

   The frozen sheet answered this by hand-editing `!important` onto its own
   `.bg-transparent`. This is the same answer without the force: declared here,
   after the components, so it wins on order and can still be overridden by
   anything more specific. */
.bg-transparent {
  background-color: transparent;
}

/* --- The `xs` breakpoint ---------------------------------------------------
   Metronic's own sub-640px variant. Tailwind v4 ships no max-width variant
   below `sm`, so this used to be the whole family, hand-written — and a class
   the markup invented was served by nobody and silently did nothing.
   `appslate.tw.css` now registers `@custom-variant xs (@media (max-width:
   639px))`, so the generated layer serves them from the markup and the next
   one costs a rebuild rather than a bug report.

   `xs:hidden` is the one that could not go, and the reason was measured rather
   than assumed. The generated family ships inside `@layer utilities`, and the
   only element carrying `xs:hidden` also carries `.drawer-only`, whose
   `display: inline-flex` is unlayered — so a generated rule loses the tie and
   the header's wordmark link comes back below 640px. Folding the hide into
   `.drawer-only` is not the alternative it looks like: the same class is the
   drawer TOGGLE in the header and its dismiss button in the aside, and hiding
   those on a phone removes the only route to the navigation. So it stays here,
   unlayered, next to the rule it has to beat — the same reason
   `.bg-transparent` above is here — and `appslate.tw.css` excludes it from the
   build so there is exactly one of it. `AppslateResidualTest` pins both halves.

   `.px-2` is not an `xs:` utility but belongs to the same compaction: the
   generated `px-2` is layered, so this unlayered rule wins below 640px, which
   is the behaviour the frozen sheet had. `.menu-item .px-2` then takes it to
   zero on the nav's tabs, and that is what retired `xs:px-1` from the markup —
   at (0, 2, 0) it beat the single-class utility outright, so the class had
   never once moved a pixel in either stylesheet. */
@media (max-width: 639px) {
  .xs\:hidden {
    display: none;
  }

  .px-2 {
    padding-left: 0.25rem;
    padding-right: 0.25rem;
  }

  .menu-item .px-2 {
    padding-left: 0;
    padding-right: 0;
  }
}

/* ==========================================================================
   8. Page areas
   ========================================================================== */

/* One sub-section per page or page family. A rule earns a place here only when
   it is genuinely one page's; anything that is really a component belongs in
   section 6, merged into the family it extends rather than restated beside it.

   No view may carry a `<style>` block — the three PDF/email templates are the
   whole exception, because a print engine and a mail client have no external
   stylesheet to fetch. Two consequences of a rule living here instead of at the
   end of a page: it no longer beats every other rule automatically, and it is
   now visible to every page in the app. Which is why several sub-sections below
   are keyed on a page-root class that the view had no need of before. */

/* --------------------------------------------------------------------------
   8.1 Dashboard
   --------------------------------------------------------------------------
   The reference's `MetricCard` and `ChartPanel`, applied to the cards this
   dashboard actually builds. Nothing here is a chart *option* — colours inside
   a canvas come from `window.chartTheme()`, which is the only thing that can
   reach them.

   Every card in both grids carries `.chart-widget`, so that is the surface
   hook. `.chart-container` is NOT: `qc.blade.php` and the intelmaster layout
   both define a `.chart-container` of their own, and a rule here would reach
   them. It stays what it is — a selector the page's own JS collects cards by.

   These rules replace the `<style>` block that used to sit at the bottom of
   `dashboard.blade.php` (no view may carry one — §0 of the plan), and the
   literals it painted with: `#f8fafc`, `#cbd5e1`, `#94a3b8`, `#e2e8f0`, and a
   `bg-white` on every card. */

/* The card. `--as-radius-lg` is the reference's own tile radius, and the
   border is what carries the card on the dark themes, where a shadow on a dark
   surface is invisible. The hover lift is ours — the reference's cards are
   static — kept because these cards are draggable and the lift is the only
   affordance that says so before the pointer reaches the grip. */
.chart-widget {
  position: relative;
  overflow: visible;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius-lg);
  transition: box-shadow 0.2s ease, transform 0.1s ease, border-color 0.15s ease;
}

.chart-widget:hover {
  transform: translateY(-2px);
  border-color: var(--as-border-strong);
  box-shadow: var(--as-shadow);
}

@media (prefers-reduced-motion: reduce) {
  .chart-widget:hover {
    transform: none;
  }
}

/* --- The chart panel ---------------------------------------------------- */

/* The reference's `ChartPanel` header: title on the left, controls on the
   right, a rule beneath. `min-height` rather than a fixed height so a wrapped
   title grows the band instead of overflowing it. */
.chart-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-height: 3rem;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--as-border);
}

.chart-panel-body {
  padding: 0.5rem;
}

@media (min-width: 640px) {
  .chart-panel-header {
    padding-left: 1.25rem;
    padding-right: 1.25rem;
  }

  .chart-panel-body {
    padding: 0.75rem;
  }
}

/* `font-display text-base font-semibold text-rs-text`.

   It wraps rather than ellipsising, which the first draft did: a chart title is
   user data, several of them are long enough to be cut ("QUESTIONNAIRES DIS…"),
   and the page appends a status badge INSIDE this element when a chart's data
   source cannot take the active filters — `text-overflow` would have hidden
   exactly the thing that badge exists to say. `anywhere` is only for the
   pathological case of a title with no spaces in it. */
.chart-widget .chart-title {
  min-width: 0;
  font-family: var(--as-font-display);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--as-text);
  overflow-wrap: anywhere;
}

/* The per-card controls. The reference has one `⋯` menu; ours are five
   distinct actions and stay distinct — a dropdown here would be new behaviour,
   not a restyle.

   Revealed on hover of the card, and the reveal lives HERE rather than as a
   `group-hover:opacity-100` utility on the markup for two reasons: a utility
   would lose to any rule in this file, and the error card never carried the
   `group` class the utility needs, so its drag grip has been permanently
   invisible. Focus-within keeps them reachable from the keyboard, and a
   pointer that cannot hover (touch) gets them outright — otherwise every
   control on this page is unusable on a tablet. */
.chart-panel-actions {
  display: flex;
  align-items: center;
  gap: 0.125rem;
  flex-shrink: 0;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.chart-widget:hover .chart-panel-actions,
.chart-widget:focus-within .chart-panel-actions {
  opacity: 1;
}

@media (hover: none) {
  .chart-panel-actions {
    opacity: 1;
  }
}

.chart-panel-action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: 0.5rem;
  background-color: transparent;
  color: var(--as-text-muted);
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.chart-panel-action:hover {
  background-color: var(--as-bg-muted);
  color: var(--as-text);
}

.chart-panel-action-danger:hover {
  color: var(--as-danger);
}

.chart-widget .drag-handle {
  cursor: grab;
}

.chart-widget .drag-handle:active {
  cursor: grabbing;
}

/* --- The metric tile ---------------------------------------------------- */

/* `MetricCard`: a label, a display-face number, a caption — and, ours, the
   icon chip whose colours are per-card configuration in the database. The
   softer shadow is the reference's `shadow-rs/50`. */
.dash-metric {
  padding: 1rem;
  box-shadow: var(--as-shadow-soft);
}

.dash-metric-row {
  display: flex;
  align-items: center;
  gap: 0.875rem;
}

.dash-metric-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

/* A card with no icon configured leaves this empty; without the rule it still
   claims the row's gap. */
.dash-metric-icon:empty {
  display: none;
}

/* The controls overlap the tile's top-right corner rather than being given a
   row of their own, which is what the old markup's `pt-12` reserved — three
   rems of empty tile on every card, for a control that is only visible on
   hover. The text keeps clear of them instead: the strip is two 28px buttons
   inset 8px, and the tile's own padding covers 16px of that, so 3rem is what
   is left to reserve. Less, and a wrapped label runs under the grip. */
.dash-metric-text {
  flex: 1 1 auto;
  min-width: 0;
  padding-right: 3rem;
}

.dash-metric-actions {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 10;
}

/* The label — `text-[11px] font-semibold uppercase tracking-wide
   text-rs-text-muted`. It has to beat `.chart-widget .chart-title` above (the
   panel's 16px display face), and it does so on specificity rather than on
   source order: a tie decided by position would turn every metric label back
   into a 16px heading the day someone moves a block. */
.chart-widget.dash-metric .chart-title {
  font-family: var(--as-font-body);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--as-text-muted);
}

/* The number and its caption. Both are author HTML — a template stored per
   card in the database, carrying its own `text-4xl font-semibold
   text-gray-900` — so they are addressed by POSITION, and the selector's two
   components outrank the utility classes inside them. Only the type is taken:
   the caption's colour is left alone because the card tints it to match its
   icon, and a colour declared here would win that fight silently. */
.dash-metric-body > :first-child {
  margin: 0;
  font-family: var(--as-font-display);
  font-size: 1.75rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--as-text);
}

.dash-metric-body > :nth-child(2) {
  margin-top: 0.125rem;
  font-size: 0.75rem;
  line-height: 1.25;
}

/* The dropdown the tile's `⋯` opens is `.value-card-menu`, which is now a
   shared component in section 6 — seven card grids carry it. Nothing
   dashboard-specific is left to declare here. */

/* --- The page's own dialogs ---------------------------------------------- */

/* The dashboard builds its dialogs by hand (`fixed inset-0` + a `hidden`
   toggle) rather than through KTModal, and each of the five carried the same
   `style="z-index: 10001; background-color: rgba(0, 0, 0, 0.8)"`. The scrim is
   correct in every theme — it is a shade over the page, not a surface — but
   five copies of a stacking level is five places to get it wrong. The loading
   overlay is the one that has to sit over the rest. */
.dash-dialog {
  z-index: 10001;
  background-color: rgb(0 0 0 / 80%);
}

.dash-dialog-over {
  z-index: 10002;
}

/* --- Placeholders, empty and error states -------------------------------- */

/* The card that stands in while a chart's data is in flight. It carries the
   final card's surface so the grid does not repaint when the data lands. */
.chart-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem 1rem;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius-lg);
}

/* The ring only. The rotation is the `animate-spin` utility on the element —
   this file must not declare `animation` for it, because an unlayered
   declaration here would beat the utility and there would be two answers to
   the same question. The reduced-motion slow-down IS declared here, as a
   longhand, which is exactly what lets it win over the utility's shorthand. */
.dash-spinner {
  width: 2rem;
  height: 2rem;
  margin: 0 auto 0.5rem;
  border: 2px solid var(--as-border);
  border-bottom-color: var(--as-primary);
  border-radius: 999px;
}

@media (prefers-reduced-motion: reduce) {
  .dash-spinner {
    animation-duration: 2.4s;
  }
}

/* The drop target's empty state, and the three states Sortable paints while a
   card is being dragged. */
#chartsGrid {
  min-height: 100px;
}

#chartsGrid:empty::after {
  content: 'Drag and drop charts here';
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0.5rem;
  padding: 2rem;
  border: 2px dashed var(--as-border);
  border-radius: var(--as-radius);
  color: var(--as-text-muted);
  font-size: 1.125rem;
}

/* Scoped to the cards on purpose. These three names are Sortable's defaults,
   and the app runs two more Sortable instances — the object builder's
   question cards and the sidebar's favourites, each with rules of its own
   that declare less than these do. Unscoped, the moment they left the
   view's `<style>` block for a stylesheet every page loads, the dashboard's
   drag styling would have been theirs too. */
.chart-widget.sortable-ghost {
  opacity: 0.5;
  background-color: var(--as-bg-muted);
  border: 2px dashed var(--as-border-strong);
}

.chart-widget.sortable-chosen {
  transform: scale(1.01);
  box-shadow: var(--as-shadow);
  z-index: 10;
}

.chart-widget.sortable-drag {
  opacity: 1;
  transform: rotate(1deg);
  box-shadow: var(--as-shadow);
}

/* The six-tile KPI row goes six-wide only above 2xl: at 1536px the icon chip
   and the longer labels still overflow their tile. The JS ladder adds this
   class for exactly six cards; every other count uses the token ladder in the
   markup. */
@media (min-width: 1700px) {
  .kpi-row-6-wide {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }
}

/* The count beside the toolbar's Filters button, and the button's own active
   state. Both were `purple-500`/`purple-700`/`purple-100` — a palette this app
   does not otherwise use, pale chips on the dark themes, and three class names
   the JS added and removed by hand.

   `:not(.hidden)` is not decoration. `.hidden` is a single-class rule in
   styles.css and this file loads after it, so a bare `display` here would win
   the tie and pin the badge on screen at zero filters — the same trap the
   chrome phase hit with `lg:hidden`. */
.dash-filter-count:not(.hidden) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  padding: 0 0.375rem;
  border-radius: 999px;
  background-color: var(--as-primary-muted);
  color: var(--as-primary);
  font-size: 0.6875rem;
  font-weight: 600;
  line-height: 1.25rem;
}

.btn.dash-filter-active {
  border-color: var(--as-primary);
  color: var(--as-primary);
}

/* --------------------------------------------------------------------------
   8.2 Agents
   --------------------------------------------------------------------------
   The reference's `AgentCard`, `AgentList` and the chat surface's chrome.

   Only the agents INDEX lives here. The chat panel keeps its own stylesheet
   (`css/agents/chat-panel.css`) — it is four thousand lines of one surface
   that three pages load and nothing else does, which is exactly the
   standalone-file case in the plan's CSS-placement rule. What changed there is
   the palette: it is read from these tokens now instead of ninety-nine hex
   literals.

   These rules replace the `<style>` block `agents/index.blade.php` used to
   carry (no view may have one) and the utilities its cards wore. */

/* The card. `.card` supplies the surface and the shadow; this is the
   reference's own box on top of it — a larger radius, a border that carries
   the card on the dark themes, and a hover that firms the border rather than
   lifting the card (these are not draggable, so there is nothing to promise). */
.agent-card {
  padding: 1rem;
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius-lg);
  transition: border-color 0.15s ease;
}

.agent-card:hover {
  border-color: var(--as-border-strong);
}

.agent-card-head {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
}

/* The icon chip's geometry. Its hue is per-agent database configuration and
   arrives as `--as-accent`, which the shared `.icon-tile` component in
   section 6 mixes — the markup carries both classes. */
.agent-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  flex-shrink: 0;
  border-radius: var(--as-radius);
  font-size: 1.25rem;
}

.agent-card-title {
  font-family: var(--as-font-display);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.4;
  color: var(--as-text);
  /* The name is a link. It wraps rather than truncating: an agent named for
     the thing it does is routinely longer than a third of a card, and a
     clipped name is not a name. */
  overflow-wrap: anywhere;
}

.agent-card-title:hover {
  color: var(--as-primary);
}

.agent-card-desc {
  margin-top: 0.25rem;
  font-size: 0.75rem;
  line-height: 1.5;
  color: var(--as-text-secondary);
  /* Two lines, then an ellipsis. The reference clamps at one; at our card
     width that cut most real descriptions mid-phrase. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* The meta grid. `gap: 1px` over a border-coloured background is the
   reference's hairline trick — the cells paint the surface and the gaps show
   the border through, so one rule draws every divider. */
.agent-card-meta {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  margin: 1rem 0 0;
  overflow: hidden;
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  background-color: var(--as-border);
}

.agent-card-meta > div {
  padding: 0.5rem 0.75rem;
  background-color: var(--as-bg-elevated);
}

.agent-card-meta dt {
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--as-text-muted);
}

.agent-card-meta dd {
  margin: 0.125rem 0 0;
  overflow: hidden;
  font-size: 0.75rem;
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--as-text);
}

.agent-card-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 1rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--as-border);
}

/* `mt-auto` on the foot is what keeps the action rows level across a row of
   cards whose descriptions differ in length. */
.agent-card > .agent-card-foot {
  margin-top: auto;
}

/* List mode. One markup, two shapes: `agents-list.js` swaps the grid ladder
   and sets `data-view-mode`, and the card relaxes into the reference's
   `AgentList` row — the meta facts inline instead of boxed, no divider above
   the action. */
#agents_grid[data-view-mode='list'] .agent-card {
  padding: 0.75rem 1rem;
}

#agents_grid[data-view-mode='list'] .agent-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.5rem;
  margin-top: 0.5rem;
  border: 0;
  border-radius: 0;
  background-color: transparent;
}

#agents_grid[data-view-mode='list'] .agent-card-meta > div {
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  padding: 0;
  background-color: transparent;
}

#agents_grid[data-view-mode='list'] .agent-card-meta dd {
  margin-top: 0;
}

#agents_grid[data-view-mode='list'] .agent-card-foot {
  margin-top: 0.625rem;
  padding-top: 0;
  border-top: 0;
}

/* The grid/list toggle's pressed state. Scoped to the two buttons by id
   because `.is-active` is a generic marker this app puts on tabs, nav rows and
   menu items — a rule on `.btn.is-active` would reach all of them. */
#agents_view_grid.is-active,
#agents_view_list.is-active {
  border-color: var(--as-primary);
  background-color: var(--as-primary-muted);
  color: var(--as-primary);
}

/* --- The workspace detail page ------------------------------------------- */

/* The tab strip and panel chrome are the Project detail page's
   (`css/agents/project-detail.css`); these are the rules that page does not
   have — the member / agent / file / activity lists and the read-only Details
   block. They came out of a `<style>` block in the view, so every one of them
   is scoped to `.workspace-detail`: unscoped, `.workspace-empty` would sit one
   token away from the chat surface's `.agent-workspace-empty`, and
   `.workspace-list-item` is a name any future list could pick. */
.workspace-detail .workspace-list-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--as-border);
  font-size: 0.875rem;
}

.workspace-detail .workspace-list-item:last-child {
  border-bottom: 0;
}

/* Members tab — the editable roster table workspace.js renders. Centred cells
   so a two-line name/email lines up with the single-control Access and Remove
   cells beside it. */
.workspace-detail .workspace-members-table th,
.workspace-detail .workspace-members-table td {
  vertical-align: middle;
}

.workspace-detail .workspace-empty {
  padding: 1rem;
  color: var(--as-text-muted);
  font-size: 0.875rem;
  text-align: center;
}

.workspace-detail .workspace-activity-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.workspace-detail .workspace-activity-row {
  padding: 0.4rem 0;
  border-bottom: 1px solid var(--as-border);
  color: var(--as-text-secondary);
  font-size: 0.85rem;
}

.workspace-detail .workspace-activity-row:last-child {
  border-bottom: 0;
}

.workspace-detail .workspace-activity-who {
  font-weight: 600;
  color: var(--as-text);
}

.workspace-detail .workspace-activity-project {
  font-weight: 500;
}

.workspace-detail .workspace-activity-when {
  white-space: nowrap;
}

/* Overview → Details: a labelled read-only view of the General group. Editing
   goes through the Edit modal, so these are values, not fields. */
.workspace-detail .workspace-details {
  margin: 0;
}

.workspace-detail .workspace-detail-row {
  display: grid;
  grid-template-columns: 200px 1fr;
  align-items: start;
  gap: 0.75rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid var(--as-border);
  font-size: 0.875rem;
}

.workspace-detail .workspace-detail-row:last-child {
  border-bottom: 0;
}

.workspace-detail .workspace-detail-label {
  color: var(--as-text-muted);
  font-weight: 500;
}

.workspace-detail .workspace-detail-value {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--as-text);
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

.workspace-detail .workspace-color-swatch {
  width: 0.95rem;
  height: 0.95rem;
  flex-shrink: 0;
  border: 1px solid var(--as-border);
  border-radius: 9999px;
}

@media (max-width: 640px) {
  .workspace-detail .workspace-detail-row {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }
}

/* Files tab — it reuses the Project Files split/preview chrome, whose panel
   rules assume a `.project-detail` ancestor on a chrome-free surface and force
   a viewport-filling flex layout. This page scrolls under a visible toolbar
   and tab strip, so the panel is a block with a bounded, scrollable list. */
.workspace-detail .project-detail-panel[data-panel='files']:not(.hidden),
.workspace-detail .project-detail-panel[data-panel='files']:not(.hidden) > .card,
.workspace-detail .project-detail-panel[data-panel='files']:not(.hidden) > .card > .card-body {
  display: block;
  min-height: 0;
  overflow: visible;
}

.workspace-detail #workspace_files {
  max-height: 60vh;
  overflow-y: auto;
}

/* Click-to-preview rows. The project page scopes the same interaction to
   `#project_files`, so it is mirrored rather than shared. */
.workspace-detail #workspace_files .project-row {
  padding-right: 0.7em;
  padding-left: 0.7em;
}

.workspace-detail #workspace_files .project-row[role='button'] {
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
}

.workspace-detail #workspace_files .project-row[role='button']:hover {
  background-color: var(--as-bg-muted);
}

.workspace-detail #workspace_files .project-row[role='button'].is-active {
  border-color: var(--as-primary);
  background-color: var(--as-primary-muted);
}

.workspace-detail .project-files-preview {
  min-height: 360px;
}

/* --------------------------------------------------------------------------
   8.3 Auth
   --------------------------------------------------------------------------
   The reference's `AuthLayout` and its two pages, applied to the layout every
   signed-out page in the app already shares (`layouts/masterguest`).

   The shell is one element with two background layers — the brand's photo and
   the vignette over it — where the reference stacks two absolutely positioned
   divs. Same result, and it keeps the header and the card as the layout's only
   children, so a page that scrolls scrolls the document rather than a pane.

   The photo arrives as `--as-auth-photo` from the layout, because it is a
   per-brand asset path resolved per request (`$bg_image`) and no stylesheet can
   know it. That inline custom property is the ONLY thing the layout still
   declares inline; the `<style>` block that used to carry the same URL as a
   whole rule is gone.

   Not every page on this layout wants the photo: the plans and checkout pages
   are long documents that read better on the page surface, and they used to
   blank the background out with `background-image: none !important` and a
   literal `#f8f9fb` — which left their headings, which DO follow the theme,
   invisible on both dark themes. They pick `.auth-shell-plain` now and get the
   themed page colour. */

.auth-shell {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  /* `min-height`, not `height`: a long form (register at 360px, the plans
     table) grows the shell and scrolls the document. A fixed height would clip
     it, and centring inside a scroll container hides the top of anything
     taller than the viewport. */
  min-height: 100dvh;
  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
}

/* The vignette is listed first because background layers paint front to back:
   the gradient sits ON the photo. `--as-bg-auth` is the fallback the token map
   describes — a brand with no photo gets the themed gradient rather than a
   bare surface. */
.auth-shell-photo {
  background-image: var(--as-auth-vignette), var(--as-auth-photo, var(--as-bg-auth));
}

.auth-shell-plain {
  background-color: var(--as-bg);
}

/* Three of the boxes below are toggled by a page script — the live password
   checklist, the 2FA challenge's two alternative fields — and every one of
   them declares `display`. The generated utilities ship inside
   `@layer utilities`, so an unlayered declaration here outranks `hidden` no
   matter which file loads last; the exception has to be a rule, beside the
   rules it answers. */
.auth-field.hidden,
.pw-rules.hidden {
  display: none;
}

.auth-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem;
}

@media (min-width: 640px) {
  .auth-topbar {
    padding: 1.25rem 2rem;
  }
}

.auth-brand {
  display: inline-flex;
  align-items: center;
}

.auth-brand-logo {
  width: auto;
  height: 2rem;
}

@media (min-width: 640px) {
  .auth-brand-logo {
    height: 2.25rem;
  }
}

/* Which wordmark to show is a surface question, not a theme question. On the
   photo it is always the on-dark asset — the vignette makes that surface dark
   in every theme, exactly as the sidebar is navy in every theme. Only the
   plain shell follows the theme, and that is what its two `<img>` elements are
   for: the swap has to happen after the pre-paint reader has set the
   attribute, and a `src` chosen in PHP cannot know the theme at all. */
@media screen {
  .auth-shell-photo .auth-brand-logo[data-as-brand-logo='on-light'],
  html[data-theme-mode='light'] .auth-shell-plain .auth-brand-logo[data-as-brand-logo='on-dark'],
  html[data-theme-mode='semi-dark'] .auth-shell-plain .auth-brand-logo[data-as-brand-logo='on-light'],
  html[data-theme-mode='dark'] .auth-shell-plain .auth-brand-logo[data-as-brand-logo='on-light'] {
    display: none;
  }
}

/* On paper both of those premises are gone. The photo does not print — a
   background image never does — and the theme does not survive either, because
   sections 1 and 2 pin every themed token back to its light value while
   printing. So the surface is white on both shells and in all three modes, and
   the on-dark wordmark is white ink on it. The choice is a `display`, not a
   token, so the print pin cannot reach it; this is that pin's other half.
   `.auth-shell-plain` is the billing pages, which is where someone actually
   reaches for print. */
@media print {
  .auth-brand-logo[data-as-brand-logo='on-dark'] {
    display: none;
  }
}

/* A drop shadow is what keeps a white wordmark legible where the photo peaks
   bright. The reference writes it as `drop-shadow-[0_1px_8px_rgba(0,0,0,0.45)]`;
   it belongs to the photo surface only. */
.auth-shell-photo .auth-brand-logo {
  filter: drop-shadow(0 1px 8px rgb(0 0 0 / 45%));
}

/* The theme control is the header's own `.topbar-action` — same 36px ghost
   square, same delegated `data-as-theme-toggle` handler. Over the photo it
   takes the platform's on-dark chrome colours, which carry no theme overrides,
   so the control stays legible whatever the card behind it is wearing. */
.auth-shell-photo .topbar-action {
  color: var(--as-sidebar-text);
}

.auth-shell-photo .topbar-action:hover {
  background-color: var(--as-sidebar-hover);
  color: var(--as-sidebar-text);
}

.auth-main {
  display: flex;
  flex: 1 1 auto;
  padding: 0.5rem 1rem 3rem;
}

/* The card. `.card`'s own box is deliberately not used: this is the
   reference's 420px auth panel, with the larger radius and its own padding
   step, and composing the two would have meant overriding four of its five
   declarations. */
.auth-card {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 420px;
  padding: 1.5rem;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius-lg);
  box-shadow: var(--as-shadow);
}

@media (min-width: 640px) {
  .auth-card {
    padding: 2rem;
  }
}

/* One page needs more room than the auth panel: the 2FA setup page, whose QR
   code and block of recovery codes do not read at 420px. */
.auth-card-wide {
  max-width: 32rem;
}

.auth-eyebrow {
  margin-bottom: 0.25rem;
  color: var(--as-primary);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The display face and the tightening arrive from the base `h1` rule. */
.auth-title {
  color: var(--as-text);
  font-size: 1.5rem;
  font-weight: 700;
}

.auth-subtitle {
  margin-top: 0.375rem;
  color: var(--as-text-secondary);
  font-size: 0.875rem;
}

/* The card's form region — the `<form>` itself on the six pages that have one,
   and the wrapper around several of them on the two that do not (verify-email,
   the 2FA setup page). */
.auth-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 1.75rem;
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.auth-field-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* The reference labels a field in the primary text colour rather than the
   secondary one `.form-label` carries site-wide. Re-cutting that class is the
   input family's own restyle, so the auth card asks for the design's value
   here — and the views drop the `text-gray-900` utility they used to carry to
   the same end. */
.auth-field .form-label {
  color: var(--as-text);
}

.auth-footnote {
  margin-top: 1.5rem;
  color: var(--as-text-secondary);
  font-size: 0.875rem;
  text-align: center;
}

/* The status and validation blocks. Every auth page wrote these as light
   utility chips — `bg-red-50 border-red-200 text-red-700`, `text-green-600`,
   `text-yellow-700` — which are three bright holes on a dark card. The tint is
   the semantic token at the muted alpha, the same anatomy the status pills
   use. `.alert-danger` stays on the markup: it is one of the selectors the
   login E2E reads an error out of. */
.auth-alert {
  padding: 0.625rem 0.875rem;
  border: 1px solid transparent;
  border-radius: var(--as-radius);
  font-size: 0.8125rem;
}

.auth-alert-danger {
  border-color: color-mix(in srgb, var(--as-danger) 35%, transparent);
  background-color: var(--as-danger-muted);
  color: var(--as-danger);
}

.auth-alert-success {
  border-color: color-mix(in srgb, var(--as-success) 35%, transparent);
  background-color: var(--as-success-muted);
  color: var(--as-success);
}

.auth-alert-info {
  border-color: color-mix(in srgb, var(--as-info) 35%, transparent);
  background-color: var(--as-info-muted);
  color: var(--as-info);
}

.auth-alert-warning {
  border-color: color-mix(in srgb, var(--as-warning) 35%, transparent);
  background-color: var(--as-warning-muted);
  color: var(--as-warning);
}

/* An alert holds either a list of `<p>` errors (login) or the shared
   `<x-validation-errors>` heading + `<ul>`. Both get the same tight rhythm —
   and this deliberately outranks that component's own `mt-3`, which is a
   layered utility and a page-sized gap inside a 13px box. */
.auth-alert p + p,
.auth-alert ul {
  margin-top: 0.375rem;
}

/* The 2FA setup page's three boxed regions. A QR code is an SVG of black
   modules with a transparent ground, so it needs a white plate of its own on
   every theme — a dark card behind it leaves a scanner nothing to read. The
   secret and the recovery codes were `bg-gray-100` / `bg-gray-50`, which are
   the same defect one step milder. */
.two-factor-qr {
  padding: 0.75rem;
  background-color: #ffffff;
  border-radius: var(--as-radius);
}

.two-factor-qr svg {
  display: block;
}

/* Neither declares a family: the two secrets are marked `font-mono` in the
   markup, because a component rule here may not name a face (the two design
   faces are set once, on `body` and `h1`-`h4`, and every page inherits them). */
.two-factor-secret {
  padding: 0.25rem 0.5rem;
  background-color: var(--as-bg-muted);
  border-radius: 0.375rem;
  color: var(--as-text);
}

.two-factor-codes {
  padding: 0.75rem;
  background-color: var(--as-bg-muted);
  border-radius: var(--as-radius);
  color: var(--as-text);
  font-size: 0.75rem;
  text-align: left;
}

.two-factor-section {
  margin-top: 0.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--as-border);
}

/* The live password checklist. Its three states used to be hex literals
   assigned from the page's own script (`#16a34a`, `#78829d`, `#dc2626`), which
   is a colour no theme and no stylesheet can reach; the script toggles
   `.is-met` now and the colours resolve here. The static form of the same list
   (reset-password, the profile page) is the same partial without the marks. */
/* `--as-text-secondary`, not the muted step this first took: these are
   instructions a user reads while typing, not metadata beside a value, and the
   muted token sits near 2.6:1 on the light card. */
.pw-rules {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  margin-top: 0.25rem;
  color: var(--as-text-secondary);
  font-size: 0.75rem;
}


.pw-rule.is-met {
  color: var(--as-success);
}

.pw-mark {
  display: inline-block;
  width: 1.1em;
}

.pw-match {
  margin-top: 0.25rem;
  color: var(--as-danger);
  font-size: 0.75rem;
}

.pw-match.is-met {
  color: var(--as-success);
}

/* 8.4 was Files — the file browser's page CSS. The page was retired 2026-08-10
   (docs/DEPRECATED_PAGES.md) and all 33 of its classes had lost their last
   consumer, so the section went with it. The number is left unused rather than
   renumbering 8.5-8.9, which every citation elsewhere would have to follow. */

/* --------------------------------------------------------------------------
   8.5 Tasks
   --------------------------------------------------------------------------
   The reference's `TasksPage` — board, list and schedule — applied to the four
   view modes this page already has. The calendar is ours and the reference has
   no counterpart; it is kept and restyled (plan §6 split rule). The design's
   card, grid and timeline modes are functionality, not styling
   (`MIGRATION_PLAN_FEATURES.md` F-2).

   These rules replace the 799-line `<style>` block the view used to carry (no
   view may have one — plan §0), and the palette it painted with: `#3b82f6`,
   `#2563eb`, `#1e293b`, `#64748b`, `#cbd5e1`, `#f9fafb`, `#e5e7eb`, `#F8F5FF`,
   `#5014D0`, `#EAFFF1`, `#FFF8DD`, `#DEEAFC`, `#3167E3`, `#E4405E`, plus
   `background: white` on six surfaces. 133 rules became the 103 below, and what
   did not come across was dead: five kanban column tints keyed by a hardcoded
   workflow-status **id** and the `.status-*` half that nothing ever applied,
   three declarations of `.kanban-drag-icon` and two of `.task-card.dragging`,
   `.status-badge`, `.status-pill`, four
   `.task-priority-*` (the priority pill is the platform's `.priority-*`),
   `.gantt-header-row`, `.list-gantt-task-row/-cell`, `.list-gantt-header-cell`,
   `.task-item`, `.task-bottom-rows/-dates-row/-badges-row`,
   `.kanban-column-title`, `.bar-completed`, an empty rule, and
   `.project-view-context > .flex`, whose child has been a grid since D-4.

   Two vendor libraries draw into this page — Frappe Gantt and FullCalendar —
   and both arrive from a CDN `<link>` in the view body, i.e. AFTER this file.
   Their overrides are therefore scoped under `.project-view-context`, which
   both outranks them without `!important` and keeps a rule addressing `.fc-*`
   or `.gantt` from reaching any other page that ever loads those libraries. */

/* --- Page containment ----------------------------------------------------- */

/* The tab body is one card holding four stacked views, and the whole page must
   never be what scrolls sideways — a view scrolls inside itself. */
.project-view-context {
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  overflow-x: hidden;
}

.tab-content-wrapper {
  position: relative;
  box-sizing: border-box;
  max-width: 100%;
  overflow-x: hidden;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow);
}

.view-content {
  position: relative;
  box-sizing: border-box;
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
}

/* The Gantt view is a two-pane flex column, and `#id` is how the original said
   so — kept, because `.hidden` is a layered utility and an id declaring
   `display` would otherwise win over it in both directions. */
#list-gantt-view:not(.hidden) {
  display: flex;
  flex-direction: column;
}

/* The list view renders `partials/listtable` inside the tab body, where a
   second card outline would draw a box inside a box. */
.no-outline .card,
.no-outline .card-header {
  border: none;
  border-radius: 0;
  box-shadow: none;
  outline: none;
}

.no-outline .panel-main {
  border-bottom-left-radius: var(--as-radius);
  border-bottom-right-radius: var(--as-radius);
}

/* The tab strip. `.page-tabs-link` (section 5.4) draws the tabs themselves —
   the same component the sibling-page tabs use — so a tab here reads as a tab
   anywhere. The strip is a row because the time-scale switch shares it.

   Named for the page's own context class, not `.project-tabs`: the agents
   project detail already owns `.project-tabs-meta` and `.project-tabs-overflow`
   in `css/agents/project-detail.css`, and two families one hyphen apart is a
   rule someone edits on the wrong page. */
/* `min-width: 0` is load-bearing. A grid item takes its CONTENT as its floor
   by default, so the strip refused to narrow past its four tabs, grew wider
   than the card, and the card's own `overflow-x: hidden` clipped whatever sat
   at the end — at 360 that was the Gantt's Day/Week/Month buttons, off the
   screen and unreachable. With a floor of zero the strip fits, the tab list
   scrolls and the time-scale switch keeps its place.

   The tab list needs no floor of its own: it is `overflow-x-auto`, and an item
   that establishes a scroll container already has an automatic minimum of
   zero. */
.project-view-tabs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  min-width: 0;
  padding: 0 1rem;
  border-bottom: 1px solid var(--as-border);
}

/* --- Kanban --------------------------------------------------------------- */

.kanban-board {
  display: flex;
  flex-wrap: nowrap;
  gap: 1rem;
  width: max-content;
  min-height: 500px;
  padding: 1rem;
}

/* Fixed-width columns: a board reads as a board because the columns do not
   resize with their contents. */
.kanban-column {
  display: flex;
  flex-direction: column;
  flex: 0 0 280px;
  box-sizing: border-box;
  width: 280px;
  /* No hue set resolves to the elevated surface exactly, not to a 92%-opaque
     approximation of it — which is what mixing against `transparent` would
     give. */
  background-color: color-mix(in srgb, var(--as-status, var(--as-bg-elevated)) 8%, var(--as-bg-elevated));
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow-soft);
}

/* The status hue, declared ONCE per status and read twice — by the column's
   tint and by its header icon. Written as two mappings it was the same four
   groupings in eight rules, which is four chances for a column and its own icon
   to disagree.

   It is selected by the status' slugged NAME, never by its id: ids differ per
   workflow and per install, and the five that used to be written into this file
   (`29` … `33`) were the development database's. A status not listed here has
   no hue, and the fallbacks below leave it the neutral column.

   The hue is the semantic one the same status already has as a pill in section
   6, so a column, its cards' pills and a chart series agree. The tint is mixed
   INTO the surface rather than laid over it, which is what keeps it a tint on
   the dark themes instead of a bright slab. */
.kanban-column[data-status='in-progress'] {
  --as-status: var(--as-warning);
}

.kanban-column[data-status='on-hold'],
.kanban-column[data-status='delayed'],
.kanban-column[data-status='deferred'] {
  --as-status: var(--as-info);
}

.kanban-column[data-status='completed'],
.kanban-column[data-status='done'] {
  --as-status: var(--as-success);
}

.kanban-column[data-status='escalated'] {
  --as-status: var(--as-danger);
}

.kanban-column-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0.75rem;
  color: var(--as-text);
  font-size: 0.8125rem;
  font-weight: 600;
}

.kanban-column-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The reference's column count — a pill at the end of the header. */
.kanban-column-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  min-width: 1.25rem;
  height: 1.25rem;
  margin-left: auto;
  padding: 0 0.375rem;
  border-radius: 9999px;
  background-color: var(--as-bg-muted);
  color: var(--as-text-secondary);
  font-size: 0.625rem;
  font-weight: 600;
}

/* Sized by `font-size` — see the note on `.file-type-icon`. */
.kanban-status-icon {
  flex-shrink: 0;
  color: var(--as-status, var(--as-text-secondary));
  font-size: 1rem;
}

.kanban-tasks {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  min-height: 200px;
  padding: 0.5rem;
}

/* --- The task card -------------------------------------------------------- */

.task-card {
  position: relative;
  padding: 0.85rem;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow-soft);
  /* The card is not draggable; its grip is. */
  cursor: default;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}

.task-card:hover {
  transform: translateY(-1px);
  border-color: var(--as-border-strong);
  box-shadow: var(--as-shadow);
}

.task-card.dragging {
  opacity: 0.5;
  border-style: dashed;
  border-color: var(--as-primary);
}

.task-card .task-title {
  margin-bottom: 0.5rem;
  /* Clear of the grip in the corner. */
  padding-right: 2rem;
  color: var(--as-text);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.4;
  text-align: left;
}

.task-card .task-title button {
  color: inherit;
  font-weight: inherit;
  text-align: left;
  text-decoration: none;
}

.task-card .task-title button:hover {
  color: var(--as-primary);
}

.task-card .task-meta {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-bottom: 0.375rem;
  color: var(--as-text-secondary);
  font-size: 0.75rem;
  font-weight: 400;
}

.task-bottom-area {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
  padding-top: 0.5rem;
}

.date-box {
  padding: 0.375rem 0.75rem;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: 0.25rem;
  color: var(--as-text-secondary);
  font-size: 0.75rem;
  white-space: nowrap;
}

/* Past its due date and not finished — the one state on this page that has to
   read as a warning rather than as data. The same class marks the Gantt list's
   due cell, and it is scoped to the page: `is-overdue` is a generic state name
   and a rule for it has no business reaching the rest of the app. */
.project-view-context .is-overdue {
  color: var(--as-danger);
  font-weight: 500;
}

.date-box.is-overdue {
  border-color: color-mix(in srgb, var(--as-danger) 35%, transparent);
  background-color: var(--as-danger-muted);
}

/* The grip. It used to be declared three times — twice for opacity, once for
   position — and then have every one of those declarations overridden by an
   inline style the script wrote on the same element. */
.kanban-drag-icon {
  position: absolute;
  top: 0.375rem;
  right: 0.5rem;
  z-index: 10;
  padding: 0.25rem;
  border-radius: 0.25rem;
  background-color: var(--as-bg-muted);
  color: var(--as-text-secondary);
  opacity: 0.6;
  cursor: grab;
  transition: opacity 0.2s ease;
}

.kanban-drag-icon:active {
  cursor: grabbing;
}

.task-card:hover .kanban-drag-icon {
  opacity: 1;
}

/* Where the card will land. */
.drop-indicator {
  height: 4px;
  margin: 8px 0;
  border-radius: 2px;
  background-color: var(--as-primary);
  opacity: 0.8;
  animation: pulse-drop-zone 1.5s ease-in-out infinite;
}

@keyframes pulse-drop-zone {
  0%,
  100% {
    opacity: 0.6;
  }

  50% {
    opacity: 1;
  }
}

/* Dragging opens the gaps between cards so a drop target is easier to hit. */
.kanban-tasks.drag-active .task-card:not(.dragging) {
  margin-bottom: 12px;
  transition: margin 0.2s ease;
}

/* The board's own empty states. Three of them used to be written as a
   40-declaration inline `style` attribute each, `background: white` and
   `color: #555` included. */
.board-empty {
  padding: 1rem;
  color: var(--as-text-secondary);
  text-align: center;
}

.board-empty-float {
  position: absolute;
  top: 30%;
  left: 50%;
  z-index: 10;
  transform: translate(-50%, -50%);
  width: auto;
  max-width: 90%;
  padding: 2rem;
  background-color: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow-soft);
}

@media (prefers-reduced-motion: reduce) {
  .task-card,
  .kanban-drag-icon,
  .kanban-tasks.drag-active .task-card:not(.dragging) {
    transition: none;
  }

  .task-card:hover {
    transform: none;
  }

  .drop-indicator {
    animation: none;
  }
}

/* --- Gantt ---------------------------------------------------------------- */

/* The two panes are a row, and the row never scrolls as a whole — the list
   pane scrolls vertically and the chart pane horizontally. */
#list-gantt-view-container {
  display: flex;
  flex: 1;
  flex-direction: row;
  min-height: 0;
  overflow: hidden;
}

#list-gantt-list-pane {
  width: 45%;
  min-width: 300px;
  max-width: 500px;
  height: 100%;
  overflow-y: auto;
  border-right: 1px solid var(--as-border);
}

#list-gantt-list-pane table {
  font-size: 0.75rem;
}

/* The header band matches the Gantt's own 60px one, row for row, so the two
   panes line up. */
.list-gantt-head {
  position: sticky;
  top: 0;
  z-index: 10;
  background-color: var(--as-bg-muted);
}

.list-gantt-head th {
  height: 60px;
  border-bottom: 1px solid var(--as-border);
}

.list-gantt-label {
  color: var(--as-text-secondary);
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1rem;
}

.list-gantt-row {
  height: 43px;
}

.list-gantt-row:hover {
  background-color: var(--as-bg-muted);
}

#list-gantt-list-pane td {
  height: 43px;
  padding: 0.25rem 0.25rem 0.25rem 0.5rem;
  border-bottom: 1px solid var(--as-border);
  vertical-align: middle;
}

#list-gantt-list-pane th:first-child,
#list-gantt-list-pane td:first-child {
  padding-left: 1rem;
}

/* One line, ellipsised: a long task title must not push the two panes out of
   alignment. */
.list-gantt-title {
  max-width: 200px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.list-gantt-title button:hover {
  color: var(--as-primary);
}

#list-gantt-list-pane .select-status,
#list-gantt-list-pane .select-priority {
  white-space: nowrap;
}

#list-gantt-gantt-pane-wrapper {
  position: relative;
  flex: 1;
  min-width: 0;
}

#list-gantt-gantt-pane {
  display: flex;
  flex: 1;
  flex-direction: column;
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: auto;
  scrollbar-color: var(--as-scrollbar-thumb) var(--as-scrollbar-track);
}

/* The bar is deliberately always visible here: it is the only affordance that
   says the chart continues past the right edge. */
#list-gantt-gantt-pane::-webkit-scrollbar {
  height: 12px;
  -webkit-appearance: none;
}

#list-gantt-gantt-pane::-webkit-scrollbar-thumb {
  border-radius: 6px;
  background-color: var(--as-scrollbar-thumb);
}

#list-gantt-gantt-pane::-webkit-scrollbar-track {
  background-color: var(--as-scrollbar-track);
}

/* The two edge shadows the script fades in and out with `style.opacity`; the
   box itself was another inline style, one per side. */
.gantt-scroll-shadow {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 5;
  width: 10px;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.gantt-scroll-shadow-left {
  left: 0;
  background: linear-gradient(to right, rgb(0 0 0 / 10%), transparent);
}

.gantt-scroll-shadow-right {
  right: 0;
  background: linear-gradient(to left, rgb(0 0 0 / 10%), transparent);
}

.gantt-container {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 100%;
  padding: 0;
  /* The pane scrolls, not the chart. */
  overflow: visible;
}

/* --- Frappe Gantt (vendor) ------------------------------------------------ */

/* Everything below addresses markup the library draws. `.project-view-context`
   is what outranks the CDN stylesheet; the fills are the same tokens the page's
   own script hands the SVG through `window.chartTheme()`. */
.project-view-context .gantt .grid-background {
  fill: var(--as-bg-elevated);
}

.project-view-context .gantt .grid-header {
  fill: var(--as-bg-muted);
  stroke: none;
}

/* Frappe stripes alternate rows; this page draws its own row lines instead. */
.project-view-context .gantt .grid-row:nth-child(2n),
.project-view-context .gantt .grid-row:nth-child(2n + 1) {
  fill: transparent;
}

.project-view-context .gantt .row-line {
  stroke: var(--as-border);
}

.project-view-context .gantt .lower-text,
.project-view-context .gantt .upper-text {
  fill: var(--as-text-muted);
  font-weight: 600;
  text-anchor: start;
  text-transform: uppercase;
}

.project-view-context .gantt .bar {
  fill: var(--as-primary-muted);
  stroke: none;
}

.project-view-context .gantt .bar-progress {
  fill: var(--as-primary);
}

.project-view-context .gantt .bar-label {
  fill: var(--as-primary);
  font-size: 0.75rem;
  font-weight: 500;
}

/* A finished task. The script has always chosen this class for Completed /
   Done / Closed and no rule ever drew it — the bar was indistinguishable from
   an open one. `.bar-completed`, which two rules DID draw, is a class nothing
   emits. */
.project-view-context .gantt .bar-inactive .bar {
  fill: var(--as-success-muted);
}

.project-view-context .gantt .bar-inactive .bar-progress {
  fill: var(--as-success);
}

.project-view-context .gantt .bar-inactive .bar-label {
  fill: var(--as-success);
}

.project-view-context .gantt .bar-wrapper:hover .bar,
.project-view-context .gantt .bar-wrapper.active .bar {
  fill: color-mix(in srgb, var(--as-primary) 35%, transparent);
}

.project-view-context .gantt .bar-wrapper.active .bar-progress {
  fill: var(--as-primary-hover);
}

/* The resize handles. */
.project-view-context .gantt .handle {
  width: 10px;
  fill: var(--as-primary);
  opacity: 0.8;
  cursor: ew-resize;
}

.project-view-context .gantt .handle:hover,
.project-view-context .gantt .bar-wrapper:hover .handle {
  opacity: 1;
}

/* The library's own hover popup is replaced by this page's modal. */
.project-view-context .frappe-gantt-popup,
.project-view-context .popup-wrapper,
.project-view-context .details-container {
  display: none;
}

/* --- Calendar (FullCalendar, vendor) -------------------------------------- */

#calendar-view-container,
#list-gantt-view {
  min-height: 600px;
  margin: 0;
  padding: 0;
  border-bottom-right-radius: var(--as-radius);
  border-bottom-left-radius: var(--as-radius);
}

.project-view-context .fc-theme-standard td,
.project-view-context .fc-theme-standard th {
  border: 1px solid var(--as-border);
}

.project-view-context .fc .fc-event {
  font-size: 0.8em;
  cursor: pointer;
}

.project-view-context .fc .fc-daygrid-day-frame {
  padding: 0.25rem;
}

.project-view-context .fc-daygrid-event-dot {
  border-color: currentcolor;
}

.project-view-context .fc-event-main-frame {
  overflow: hidden;
  text-overflow: ellipsis;
}

.project-view-context .fc-event-title {
  overflow: hidden;
  padding-left: 0.75rem;
  font-size: 0.7rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.project-view-context .fc-col-header-cell {
  background-color: var(--as-bg-muted);
  font-size: 0.8125rem;
}

.project-view-context .fc-col-header-cell-cushion {
  color: var(--as-text-secondary);
  font-size: 0.875rem;
  font-style: normal;
  font-weight: 400;
  line-height: 1.25rem;
  text-align: right;
}

.project-view-context .fc-timegrid-axis-cushion,
.project-view-context .fc-timegrid-slot-label-cushion {
  font-size: 0.8125rem;
}

/* Today. It was a `#E4405E` chip — the old danger red, on a day that is not an
   error. It is the accent now, which is also what the mini calendars elsewhere
   in the app mark today with. */
.project-view-context .fc-day-today .fc-daygrid-day-number {
  display: inline-block;
  min-width: 1.5rem;
  margin: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: 0.375rem;
  background-color: var(--as-primary);
  color: var(--as-primary-inverse);
  font-weight: 600;
  text-align: center;
}

/* The toolbar. Its view buttons are hidden — the strip at the top of the page
   is what switches scale — so what is left is the month title and the two
   arrows, centred as a unit. */
.project-view-context .fc .fc-toolbar.fc-header-toolbar {
  position: relative;
  margin-top: 1rem;
  margin-bottom: 1rem;
}

.project-view-context .fc-toolbar .fc-toolbar-title {
  min-width: 40px;
  padding: 0 0.25rem;
  color: var(--as-text);
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.5rem;
  text-align: center;
  vertical-align: middle;
  white-space: nowrap;
}

.project-view-context .fc-toolbar .fc-toolbar-chunk:first-child {
  position: relative;
  z-index: 1;
}

.project-view-context .fc-toolbar .fc-toolbar-chunk:nth-child(2) {
  position: absolute;
  left: 50%;
  z-index: 0;
  transform: translateX(-50%);
}

.project-view-context .fc-toolbar .fc-toolbar-chunk:last-child {
  display: none;
}

.project-view-context .fc-button-group {
  margin-left: 1rem;
}

/* The prev/next arrows, as ghost buttons rather than the library's filled
   blue ones. */
.project-view-context .fc-toolbar .fc-button,
.project-view-context .fc-toolbar .fc-button-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0.5rem;
  border: 0;
  border-radius: 0.375rem;
  background-color: transparent;
  box-shadow: none;
  color: var(--as-text-secondary);
  font-size: 0.8125rem;
  line-height: 1;
  text-transform: none;
  vertical-align: middle;
}

.project-view-context .fc-toolbar .fc-button:hover {
  background-color: var(--as-bg-muted);
  color: var(--as-text);
}

/* A row whose status was just changed elsewhere on the page. */
.project-view-context .row-flash {
  background-color: var(--as-warning-muted);
  transition: background-color 0.3s ease;
}

/* --------------------------------------------------------------------------
   8.6 The object builder
   --------------------------------------------------------------------------
   `objectbuilder.blade.php` and `partials/builderfields.blade.php`: a section
   rail beside a list of question cards, which the builder can also present as
   a chat transcript or a one-question-at-a-time wizard (`data-viewmode`).

   The design reference never mocked this page, so this is a MOVE and not a
   restyle — the layout is the one that was rendering. Its colours did change:
   the frozen sheet wrote them as light-theme literals, so the page stayed the
   same shade of white whichever theme was on. They read the tokens now.
   -------------------------------------------------------------------------- */

@media (min-width: 768px) {
  .panel-container.ob-builder {
    grid-template-columns: 280px 1fr;
  }
}

@media (min-width: 1024px) {
  .panel-container.ob-builder {
    grid-template-columns: 300px 1fr;
  }
}

.ob-builder .ob-section-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0 1rem 1rem;
}

.ob-section-btn {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  padding: 0.75rem;
  cursor: pointer;
  transition: all 0.15s ease;
  background: transparent;
}

.ob-section-btn:hover {
  border-color: color-mix(in srgb, var(--as-primary) 40%, transparent);
  background: var(--as-gray-50);
}

.ob-section-btn.active {
  border-color: color-mix(in srgb, var(--as-primary) 60%, transparent);
  background: color-mix(in srgb, var(--as-primary) 5%, transparent);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.ob-section-btn .ob-status-text {
  font-size: 0.75rem;
  color: var(--as-text-muted);
  margin-top: 0.25rem;
}

.ob-progress-bar {
  height: 6px;
  background: var(--as-border);
  border-radius: 3px;
  overflow: hidden;
}

.ob-progress-bar-fill {
  height: 100%;
  background: var(--as-primary);
  border-radius: 3px;
  transition: width 0.3s ease;
  min-width: 0;
}

.ob-progress-badge {
  font-size: 10px;
  text-transform: uppercase;
  border: 1px solid var(--as-border);
  border-radius: 0.25rem;
  padding: 1px 6px;
  color: var(--as-gray-600);
  white-space: nowrap;
  flex-shrink: 0;
}

.ob-section-header-label {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--as-gray-600);
  margin-bottom: 0.25rem;
}

.ob-section-header-title {
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.3;
}

.ob-section-header-pill {
  border: 1px solid var(--as-border);
  border-radius: 9999px;
  padding: 0.5rem 1rem;
  font-size: 0.75rem;
  color: var(--as-gray-600);
  background: var(--as-bg-elevated);
  white-space: nowrap;
  flex-shrink: 0;
}

/* --- The question card ---------------------------------------------------- */
.ob-builder .ob-question-card {
  border: 1px solid var(--as-border);
  border-radius: var(--as-radius);
  padding: 1rem;
  background: var(--as-bg-elevated);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  position: relative;
  transition: background-color 0.2s ease;
}

/* The card stacks its label over its control instead of sitting them side by
   side, so the utilities the shared field partial writes are overridden here
   rather than branched on in the Blade. */
.ob-builder .ob-question-card .flex.items-start {
  flex-direction: column;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.ob-builder .ob-question-card .form-label.max-w-56 {
  max-width: none;
  flex-shrink: unset;
}

.ob-builder .ob-question-card .flex-1.min-w-0 {
  width: 100%;
}

.ob-builder .panel-main .objecttable.card {
  box-shadow: none;
  border: none;
  background: transparent;
}

/* The per-card edit affordances, revealed on hover. */
.ob-question-actions {
  position: absolute;
  top: 6px;
  right: 6px;
  opacity: 0;
  transition: opacity 0.2s ease;
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-border);
  border-radius: 6px;
  padding: 2px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
  z-index: 5;
}

.ob-question-card:hover .ob-question-actions {
  opacity: 1;
}

.ob-drag-handle,
.ob-edit-btn,
.ob-delete-btn {
  padding: 4px;
  border-radius: 4px;
  transition: all 0.15s ease;
  border: none;
  background: transparent;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ob-drag-handle {
  color: var(--as-text-muted);
  cursor: grab;
}

.ob-drag-handle:hover {
  color: var(--as-gray-700);
  background: var(--as-bg-muted);
}

.ob-drag-handle:active {
  cursor: grabbing;
}

.ob-edit-btn {
  color: var(--as-gray-600);
}

.ob-edit-btn:hover {
  color: var(--as-text-secondary);
  background: var(--as-bg-muted);
}

.ob-delete-btn {
  color: var(--as-danger);
}

.ob-delete-btn:hover {
  color: var(--as-danger-hover);
  background: var(--as-danger-muted);
}

/* SortableJS's three drag states. */
.ob-question-card.sortable-ghost {
  opacity: 0.4;
  background: var(--as-gray-50);
  border: 2px dashed var(--as-border-strong);
}

.ob-question-card.sortable-chosen {
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  z-index: 10;
}

/* Forced because SortableJS writes the dragged clone's opacity as an inline
   style, which no selector can outrank. */
.ob-question-card.sortable-drag {
  opacity: 1 !important;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}

/* --- The view-mode switch ------------------------------------------------- */
.ob-viewmode-toggle {
  display: inline-flex;
  border: 1px solid var(--as-border);
  border-radius: 0.5rem;
  overflow: hidden;
}

/* The buttons also carry `.btn.btn-sm`, whose horizontal padding used to win
   by loading later. The port reverses that, so only the vertical padding is
   spelled here and the ladder keeps the rest. */
.ob-viewmode-btn {
  border: none;
  border-radius: 0;
  padding-top: 4px;
  padding-bottom: 4px;
  background: transparent;
  color: var(--as-gray-600);
  transition: all 0.15s ease;
  box-shadow: none;
}

.ob-viewmode-btn + .ob-viewmode-btn {
  border-left: 1px solid var(--as-border);
}

.ob-viewmode-btn.active {
  background: var(--as-primary-muted);
  color: var(--as-primary);
}

.ob-viewmode-btn:hover:not(.active) {
  background: var(--as-bg-muted);
}

/* --- Chat and step views --------------------------------------------------
   One question at a time: answered questions become bubbles above, and only
   the live card is shown. */
.ob-builder[data-viewmode="chat"] .objecttable .card-body[data-accordion] > .ob-question-card,
.ob-builder[data-viewmode="step"] .objecttable .card-body[data-accordion] > .ob-question-card {
  display: none;
}

.ob-builder[data-viewmode="chat"] .objecttable .card-body[data-accordion] > .ob-question-card.ob-chat-active {
  display: block;
  border: 2px solid var(--as-primary);
  border-radius: var(--as-radius);
}

.ob-builder[data-viewmode="step"] .objecttable .card-body[data-accordion] > .ob-question-card.ob-step-active {
  display: block;
}

.ob-chat-container {
  display: none;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1rem;
}

.ob-builder[data-viewmode="chat"] .ob-chat-container {
  display: flex;
}

.ob-chat-section-header {
  font-size: 0.8rem;
  color: var(--as-gray-600);
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--as-border);
  margin-bottom: 0.25rem;
}

.ob-chat-bubble {
  max-width: 85%;
  padding: 0.625rem 0.875rem;
  border-radius: 1rem;
  font-size: 0.875rem;
  line-height: 1.5;
  animation: ob-chat-fadein 0.2s ease;
}

@keyframes ob-chat-fadein {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

.ob-chat-bubble--question {
  align-self: flex-start;
  background: var(--as-bg-muted);
  color: var(--as-text-secondary);
  border-bottom-left-radius: 0.25rem;
}

.ob-chat-bubble--question strong {
  color: var(--as-gray-600);
  font-weight: 600;
}

.ob-chat-bubble--answer {
  align-self: flex-end;
  background: var(--as-primary);
  color: var(--as-primary-inverse);
  border-bottom-right-radius: 0.25rem;
  cursor: pointer;
  transition: opacity 0.15s;
}

.ob-chat-bubble--answer:hover {
  opacity: 0.85;
}

.ob-chat-bubble--answer .ob-chat-edit-hint {
  font-size: 0.7rem;
  opacity: 0.7;
  margin-top: 2px;
}

.ob-chat-done {
  align-self: center;
  text-align: center;
  font-size: 0.8rem;
  color: var(--as-gray-600);
  padding: 0.75rem;
}

.ob-chat-done i {
  color: var(--as-success);
  margin-right: 0.25rem;
}

/* --- The guided-mode footer ----------------------------------------------- */
.ob-chat-next-area,
.ob-step-nav {
  display: none;
  align-items: center;
  padding: 0.875rem 1rem;
  border-top: 1px solid var(--as-border);
  background: var(--as-gray-50);
  border-radius: 0 0 var(--as-radius) var(--as-radius);
  margin-top: 0.5rem;
}

.ob-chat-next-area {
  justify-content: flex-end;
}

.ob-step-nav {
  justify-content: space-between;
}

.ob-builder[data-viewmode="chat"] .ob-chat-next-area,
.ob-builder[data-viewmode="step"] .ob-step-nav {
  display: flex;
}

.ob-step-nav-counter {
  font-size: 0.8rem;
  color: var(--as-gray-600);
  font-weight: 500;
}

/* Both guided modes carry their own save control in the footer above. */
.ob-builder[data-viewmode="chat"] .ob-save-progress-bottom,
.ob-builder[data-viewmode="step"] .ob-save-progress-bottom {
  display: none;
}

@media (min-width: 768px) {
  .ob-builder[data-viewmode="chat"] .panel-main,
  .ob-builder[data-viewmode="step"] .panel-main {
    max-height: calc(100vh - 180px);
    overflow-y: auto;
  }
}

/* A required question the builder refused to submit. The card takes the
   danger border and the label the danger text, so the reason is visible
   whether the eye lands on the frame or on the words. */
.ob-required-missing {
  border-color: var(--as-danger-hover);
  box-shadow: 0 0 0 1px var(--as-danger-hover);
}

.ob-required-missing label.form-label {
  color: var(--as-danger-hover);
}

/* --------------------------------------------------------------------------
   8.7 Risk assessment
   --------------------------------------------------------------------------
   `ra/scoring.blade.php` (the scale, weight and matrix editors) and
   `ra/riskmatrix.blade.php` + `ra/riskmatrixdetail.blade.php` (the rendered
   matrix, its filters and the override dialog). Another surface the reference
   never mocked: moved, not restyled, with the light-theme literals swapped for
   tokens so the pages follow the theme.
   -------------------------------------------------------------------------- */

/* --- The scoring page's tabs ---------------------------------------------- */
.scoring-tab-btn {
  border-bottom: 2px solid transparent;
  padding: 0.75rem 1.25rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--as-gray-600);
  cursor: pointer;
  background: none;
  transition: all 0.15s;
}

.scoring-tab-btn:hover {
  color: var(--as-text-secondary);
  border-color: var(--as-border-strong);
}

.scoring-tab-btn.active {
  color: var(--as-primary);
  border-color: var(--as-primary);
}

.scoring-tab-content {
  display: none;
}

.scoring-tab-content.active {
  display: block;
}

/* --- The banded scale, with draggable boundaries --------------------------
   Each band's fill is set inline from the band's own colour, so only the
   label treatment is here — white over whatever that colour is, which is what
   the text shadow is for. */
.scale-bar {
  position: relative;
  height: 40px;
  border-radius: 8px;
  overflow: visible;
  user-select: none;
  margin: 0.5rem 0 2rem;
}

.scale-band {
  position: absolute;
  top: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7rem;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  overflow: hidden;
  pointer-events: none;
}

.scale-divider {
  position: absolute;
  top: -4px;
  width: 32px;
  height: 48px;
  cursor: col-resize;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
}

.scale-divider-handle {
  width: 4px;
  height: 24px;
  background: #fff;
  border-radius: 2px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  pointer-events: none;
}

.scale-divider:hover .scale-divider-handle {
  background: var(--as-primary);
  width: 6px;
}

.scale-boundary-label {
  position: absolute;
  top: 46px;
  transform: translateX(-50%);
  z-index: 5;
}

.scale-boundary-label input {
  width: 42px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--as-gray-600);
  background: transparent;
  border: none;
  padding: 2px 0;
  cursor: default;
  -moz-appearance: textfield;
}

.scale-boundary-label input:hover,
.scale-boundary-label input:focus {
  color: var(--as-primary);
  outline: none;
  cursor: text;
}

/* --- The weight grid ------------------------------------------------------
   Four tracks the rows join with `display: contents`, so every row's label,
   slider, raw value and balanced value line up as columns. */
.weight-section {
  display: grid;
  grid-template-columns: 1fr 42px 1fr 54px;
  gap: 0.25rem 0.5rem;
  align-items: center;
}

.weight-row {
  display: contents;
}

.weight-label {
  font-size: 0.85rem;
  color: var(--as-text-secondary);
  white-space: nowrap;
  padding: 0.5rem 0;
  display: flex;
  align-items: center;
  overflow: hidden;
  min-width: 0;
}

/* The dotted leader that runs from the label to its slider. */
.weight-label::after {
  content: '';
  flex: 1;
  border-bottom: 1px dotted var(--as-border-strong);
  margin-left: 0.5rem;
  min-width: 0.75rem;
}

.weight-slider {
  width: 100%;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  background: var(--as-border);
  border-radius: 2px;
  outline: none;
}

.weight-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--as-primary);
  cursor: pointer;
}

.weight-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--as-primary);
  cursor: pointer;
  border: none;
}

.weight-raw {
  width: 42px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--as-gray-600);
  background: transparent;
  border: none;
  padding: 2px 0;
  -moz-appearance: textfield;
}

.weight-raw:hover,
.weight-raw:focus {
  color: var(--as-primary);
  outline: none;
}

.scale-boundary-label input::-webkit-outer-spin-button,
.scale-boundary-label input::-webkit-inner-spin-button,
.weight-raw::-webkit-outer-spin-button,
.weight-raw::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.weight-balanced {
  width: 54px;
  text-align: center;
  font-size: 0.8rem;
  color: var(--as-gray-600);
  padding: 0.25rem 0;
  font-weight: 500;
}

/* The raw weight is the input; the balanced percentage is what is shown. */
.weight-value {
  display: none;
}

.weight-total {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.8rem;
  padding: 0.2rem 0.5rem;
  border-radius: 9999px;
}

.weight-total.ok {
  background: var(--as-success-muted);
  color: var(--as-success);
}

.weight-total.warn {
  background: var(--as-warning-muted);
  color: var(--as-warning);
}

/* --- The likelihood/impact matrix ----------------------------------------- */
.matrix-grid {
  display: grid;
  gap: 2px;
}

.matrix-cell {
  padding: 0;
  border: none;
}

/* Each cell IS its select, and the band colour is set inline, so the text is
   white over whatever that turns out to be. */
.matrix-cell select {
  width: 100%;
  height: 100%;
  min-height: 40px;
  border: 1px solid var(--as-border);
  text-align: center;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  border-radius: 0;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.matrix-header,
.matrix-row-header {
  display: flex;
  align-items: center;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--as-gray-600);
}

.matrix-header {
  justify-content: center;
  padding: 0.4rem;
  text-align: center;
}

.matrix-row-header {
  justify-content: flex-end;
  padding: 0.4rem 0.75rem;
}

/* --- The formula preview -------------------------------------------------- */
.preview-input {
  width: 80px;
  text-align: center;
  padding: 0.3rem;
  border: 1px solid var(--as-input-border);
  border-radius: 4px;
  font-size: 0.9rem;
}

.preview-result {
  display: inline-flex;
  align-items: center;
  padding: 0.4rem 1rem;
  border-radius: 9999px;
  font-weight: 600;
  font-size: 0.9rem;
  color: #fff;
}

.formula-box {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9rem;
  padding: 1rem;
  background: var(--as-gray-50);
  border-radius: 8px;
  border: 1px solid var(--as-border);
  flex-wrap: wrap;
}

.formula-var {
  font-weight: 600;
  color: var(--as-primary);
}

.formula-op {
  color: var(--as-gray-600);
}

/* --- The rendered matrix -------------------------------------------------- */
/* The risk-matrix rating badges are `.pill.pill--outline`s (section 6): the
   band colour arrives as an inline `style`, because the bands are configurable
   data rather than a fixed set of classes. */

/* A score the assessor overrode, marked so it still reads as "not what the
   model said". */
.pill.rm-overridden {
  outline: 1px dashed var(--as-border);
  outline-offset: 0;
}

.rm-progress-track {
  height: 0.5rem;
  border-radius: 9999px;
  background: var(--as-border);
  overflow: hidden;
}

.rm-progress-bar {
  height: 100%;
  border-radius: 9999px;
  transition: width 0.3s ease;
}

.rm-summary-card {
  border-radius: var(--as-radius);
  border: 1px solid var(--as-border);
  background: var(--as-gray-50);
  padding: 1rem 1.25rem;
}

.rm-summary-label {
  font-size: 0.75rem;
  color: var(--as-gray-600);
}

.rm-summary-value {
  margin-top: 0.5rem;
  font-size: 1.5rem;
  font-weight: 600;
}

.rm-table th {
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--as-gray-600);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.75rem 1rem;
  text-align: left;
  border-bottom: 1px solid var(--as-border);
}

.rm-table td {
  padding: 0.875rem 1rem;
  vertical-align: middle;
  border-bottom: 1px solid var(--as-bg-muted);
}

.rm-table tbody tr {
  transition: background-color 0.15s;
}

.rm-table tbody tr:hover {
  background-color: var(--as-gray-50);
}

.rm-row.is-hidden {
  display: none;
}

/* --- The detail page's tabs and filters ----------------------------------- */
.rmd-tabs {
  display: flex;
  border-bottom: 2px solid var(--as-border);
  gap: 0;
}

.rmd-tab {
  padding: 0.625rem 1.25rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--as-gray-600);
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  cursor: pointer;
  background: none;
  border-top: none;
  border-left: none;
  border-right: none;
  transition: color 0.15s, border-color 0.15s;
}

.rmd-tab:hover {
  color: var(--as-text-secondary);
}

.rmd-tab.rmd-tab-active {
  color: var(--as-primary);
  border-bottom-color: var(--as-primary);
}

.rmd-filter-group {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.rmd-filter-label {
  font-size: 0.6875rem;
  font-weight: 500;
  color: var(--as-gray-600);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* Also carries `.select`, which supplies the surface, border, radius and type
   scale — only the density and the width floor are this filter's own. */
.rmd-filter-select {
  padding: 0.375rem 1.75rem 0.375rem 0.5rem;
  min-width: 120px;
}

.rmd-filter-select:disabled {
  cursor: not-allowed;
}

/* --- Grouped rows, in both the matrix and the detail tables ---------------- */
.rmd-group-header,
.drm-group-header {
  background: var(--as-bg-muted);
}

.rmd-group-header td,
.drm-group-header td {
  padding: 0.5rem 1rem;
  border-bottom: 1px solid var(--as-border);
  font-weight: 600;
  font-size: 0.8125rem;
  color: var(--as-text-secondary);
}

.rm-table tbody tr.rmd-group-header:hover,
.rm-table tbody tr.drm-group-header:hover {
  background-color: var(--as-bg-muted);
}

.rmd-toggle {
  cursor: pointer;
  user-select: none;
}

.rmd-chevron {
  display: inline-block;
  transition: transform 0.2s;
  margin-right: 0.375rem;
  font-size: 0.625rem;
}

.rmd-group-header.collapsed .rmd-chevron,
.drm-group-header.collapsed .rmd-chevron {
  transform: rotate(-90deg);
}

/* --- The control-selection panel and the override dialog ------------------ */
#drm-controls-panel {
  z-index: 1050;
}

#overrideSaveModal {
  z-index: 1100;
}

#drm-controls-panel.open,
#overrideSaveModal.open {
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.4);
}

.drm-ctrl-item {
  padding: 0.375rem 0;
  border-bottom: 1px solid var(--as-bg-muted);
}

.drm-ctrl-item:last-child {
  border-bottom: none;
}

.drm-ctrl-item.drm-ctrl-excluded,
.drm-ctrl-item.drm-ctrl-dimmed {
  opacity: 0.45;
}

.drm-ctrl-item.drm-ctrl-excluded .drm-ctrl-name {
  text-decoration: line-through;
}

/* A control the assessor added to this matrix by hand. */
.drm-ctrl-item.drm-ctrl-added {
  background: var(--as-warning-muted);
  margin: 0 -0.75rem;
  padding-left: 0.75rem;
  padding-right: 0.75rem;
  border-radius: 0.25rem;
}

#overrideSaveModal .modal-content {
  background: var(--as-bg-elevated);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow);
  width: 100%;
  max-width: 32rem;
  display: flex;
  flex-direction: column;
}

#overrideSaveModal .modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--as-border);
}

#overrideSaveModal .modal-body {
  padding: 1.5rem;
}

#overrideSaveModal .modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding: 1rem 1.5rem;
  border-top: 1px solid var(--as-border);
}

/* Also carries `.select`. The transition is widened to include the control's
   own focus ring, which `.select` animates and this rule would otherwise
   replace outright. */
.override-select {
  transition: background-color 0.15s, color 0.15s, border-color 0.15s,
    box-shadow 0.15s ease;
}

/* --- The risk matrix ------------------------------------------------------
   `ra/riskmatrix` and `ra/riskmatrixdetail` carried an identical copy of this
   each. The first and last columns pin while the grid scrolls sideways, and a
   gradient appears on the left column's edge once there is anything scrolled
   behind it (the scroll container sets `.has-scroll-content-left`).

   `position: sticky` inside a table needs `border-collapse: separate`, which
   is why the table opts out of the collapsed borders every other table uses.
   The rules are keyed on `.rm-table` rather than on `.scrollable-x-auto`,
   which is a platform scroll container with many other tables in it. */
.rm-table {
  border-collapse: separate;
  border-spacing: 0;
}

.rm-table th.sticky.left-0,
.rm-table td.sticky.left-0 {
  position: sticky;
  left: 0;
}

.rm-table th.sticky.right-0,
.rm-table td.sticky.right-0 {
  position: sticky;
  right: 0;
}

.rm-table th.sticky.left-0::after,
.rm-table td.sticky.left-0::after {
  content: '';
  position: absolute;
  top: 0;
  right: -10px;
  bottom: 0;
  width: 10px;
  background: linear-gradient(to left, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.06));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.scrollable-x-auto.has-scroll-content-left .rm-table th.sticky.left-0::after,
.scrollable-x-auto.has-scroll-content-left .rm-table td.sticky.left-0::after {
  opacity: 1;
}

/* --------------------------------------------------------------------------
   8.8 The record timeline
   --------------------------------------------------------------------------
   `DataController` renders this: a centre rail with dated cards alternating
   left and right, each pointing at the rail with a CSS-triangle arrow whose
   colour encodes the event type (`.tma-*`). Those six colours are data
   categories rather than theme roles, so they stay literal; the surfaces
   around them are tokens.
   -------------------------------------------------------------------------- */
.timeline {
  position: relative;
  width: 100%;
  height: 100%;
  margin-bottom: 100px;
}

.timeline .tm-box-r,
.timeline .tm-box-l {
  position: absolute;
  width: 45%;
  font-size: 0.75rem;
  margin-top: -12px;
  border-radius: 0.25rem;
}

.timeline .tm-box-r {
  left: 50%;
  margin-left: 50px;
}

.timeline .tm-box-l {
  right: 50%;
  margin-right: 50px;
}

/* --- The rail ------------------------------------------------------------- */
.timeline .tm-box-m {
  grid-column: 2;
  position: relative;
  height: 100%;
  width: 100px;
  display: flex;
  justify-content: center;
  margin: 0 auto;
}

.timeline .tm-box-m hr {
  width: 0.25rem;
  height: 200px;
  margin: 0;
  border: 2px solid var(--as-border-strong);
  position: relative;
}

/* The rail's end caps. */
.timeline .tm-box-m:after,
.timeline .tm-box-m:before {
  content: '';
  width: 40px;
  height: 2px;
  position: absolute;
  border-bottom: 4px solid var(--as-border-strong);
}

.timeline .tm-box-m:after { bottom: -2px; }
.timeline .tm-box-m:before { top: -2px; }

.timeline .tm-svg {
  position: absolute;
  width: 100px;
  height: 100%;
  top: 0;
  left: 0;
}

.timeline .tm-dot {
  position: absolute;
  width: 24px;
  height: 24px;
  background-color: var(--as-border-strong);
  border-radius: 50%;
  color: var(--as-bg-elevated);
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
}

.timeline .tm-dot i {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  text-align: center;
  font-size: 1rem;
  line-height: 1;
  color: var(--as-bg-elevated);
}

/* --- The card ------------------------------------------------------------- */
.timeline .tm-card {
  box-shadow: rgba(0, 0, 0, 0.15) 0 3px 9px 1px, rgba(0, 0, 0, 0.15) 0 0 0 1px;
  position: relative;
  padding: 0.5rem;
  background-color: var(--as-bg-elevated);
  border-radius: 0.25rem;
}

.timeline .tm-date,
.timeline .tm-title {
  position: absolute;
  top: 0;
  font-size: 0.6rem;
}

.timeline .tm-date { left: 0.25rem; }
.timeline .tm-title { right: 0.25rem; }

.tm-icon {
  position: absolute;
  right: 4px;
  top: 4px;
  font-size: 1.125rem;
  z-index: 1;
  color: var(--as-gray-600);
}

.tm-card .tm-icon i {
  display: block;
}

.tm-1,
.tm-2,
.tm-3 {
  position: relative;
  padding: 0;
  margin: 0;
  line-height: 1.1rem;
}

.tm-1 {
  padding-top: 8px;
}

/* --- The arrow ------------------------------------------------------------
   Two stacked triangles: the outer one takes the event-type colour, the inner
   one the card's surface, which is what makes it read as an outlined pointer
   rather than a solid wedge. */
.timeline .tm-arrow {
  position: absolute;
  top: 24px;
  width: 0;
  height: 0;
  border-top: 16px solid transparent;
  border-bottom: 16px solid transparent;
}

.timeline .tm-box-l .tm-arrow {
  left: 100%;
  border-left: 16px solid var(--as-border-strong);
  transform: translate(0, -50%);
}

.timeline .tm-box-r .tm-arrow {
  left: 0;
  border-right: 16px solid var(--as-border-strong);
  transform: translate(-100%, -50%);
}

.timeline .tm-arrow:after {
  content: "";
  position: absolute;
  top: 0;
  width: 0;
  height: 0;
  border-top: 15px solid transparent;
  border-bottom: 15px solid transparent;
}

.timeline .tm-box-r .tm-arrow:after {
  left: 17px;
  border-right: 15px solid var(--as-bg-elevated);
  transform: translate(-100%, -50%);
}

.timeline .tm-box-l .tm-arrow:after {
  right: 2px;
  border-left: 15px solid var(--as-bg-elevated);
  transform: translate(0, -50%);
}

.timeline .tm-box-l .tm-arrow.tma-ad { border-left-color: #6f42c1; }
.timeline .tm-box-r .tm-arrow.tma-ad { border-right-color: #6f42c1; }
.timeline .tm-box-l .tm-arrow.tma-comm { border-left-color: #17a2b8; }
.timeline .tm-box-r .tm-arrow.tma-comm { border-right-color: #17a2b8; }
.timeline .tm-box-l .tm-arrow.tma-txn { border-left-color: #28a745; }
.timeline .tm-box-r .tm-arrow.tma-txn { border-right-color: #28a745; }
.timeline .tm-box-l .tm-arrow.tma-doc { border-left-color: #007bff; }
.timeline .tm-box-r .tm-arrow.tma-doc { border-right-color: #007bff; }
.timeline .tm-box-l .tm-arrow.tma-per { border-left-color: orange; }
.timeline .tm-box-r .tm-arrow.tma-per { border-right-color: orange; }
.timeline .tm-box-l .tm-arrow.tma-int { border-left-color: red; }
.timeline .tm-box-r .tm-arrow.tma-int { border-right-color: red; }

/* --------------------------------------------------------------------------
   8.9 The inline assistant
   --------------------------------------------------------------------------
   The message transcript every assistant surface that is NOT the agents chat
   panel shares (that one has its own file, `css/agents/chat-panel.css`): the
   dashboard assistant, the workflow chat and canvas, the chat dock modal and
   DataSlate's AI panel. The DOM is built in JS, so the class contract here is
   `dashboard-ai.js` and `agents/workflow-chat.js`, not a Blade partial.
   -------------------------------------------------------------------------- */
.chat-messages {
  flex: 1;
  overflow-y: auto;
  padding: 15px;
  scrollbar-width: thin;
  scrollbar-color: var(--as-scrollbar-thumb) var(--as-scrollbar-track);
}

.chat-messages::-webkit-scrollbar {
  width: 6px;
}

.chat-messages::-webkit-scrollbar-track {
  background: var(--as-scrollbar-track);
}

.chat-messages::-webkit-scrollbar-thumb {
  background: var(--as-scrollbar-thumb);
  border-radius: 3px;
}

.message {
  margin: 10px 0 0;
  padding: 10px;
  border-radius: 1rem;
  overflow-wrap: anywhere;
  position: relative;
  font-size: 1rem;
}

.message + .message {
  margin-top: 0.5rem;
}

.message-wrapper {
  display: flex;
  flex-direction: column;
  margin-bottom: 16px;
}

.user-message-wrapper {
  align-items: flex-end;
}

.assistant-message-wrapper {
  align-items: flex-start;
}

/* Clicking a sent message re-sends it, which the hover label explains. */
.user-message {
  background-color: var(--as-primary);
  color: var(--as-primary-inverse);
  border-radius: 1rem 1rem 0 1rem;
  max-width: 80%;
  cursor: pointer;
  transition: background-color 0.2s;
}

.user-message:hover {
  background-color: var(--as-primary-hover);
}

.user-message:hover::after {
  content: 'Click to resend';
  position: absolute;
  bottom: -20px;
  left: 1rem;
  font-size: 0.7em;
  color: var(--as-gray-600);
  opacity: 0.8;
}

.assistant-message {
  background-color: var(--as-bg-muted);
  border-radius: 1rem 1rem 1rem 0;
  max-width: 90%;
}

.message-content {
  word-wrap: break-word;
  padding-left: 5px;
  padding-right: 5px;
}

.message-timestamp-external {
  font-size: 0.75rem;
  color: var(--as-gray-600);
  margin-top: 4px;
  padding: 0;
}

.user-message-wrapper .message-timestamp-external {
  text-align: right;
}

.assistant-message-wrapper .message-timestamp-external {
  text-align: left;
}

.ai-icon-wrapper {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  background: var(--as-primary-muted);
  border-radius: 50%;
}

/* --- The composer --------------------------------------------------------- */
.input-container {
  padding: 20px;
  box-sizing: border-box;
  flex-shrink: 0;
}

.chat-input-wrapper {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--as-bg-muted);
  border: 1px solid var(--as-border);
  border-radius: 0.5rem;
  padding: 10px 12px;
  transition: all 0.2s ease;
}

.chat-input-wrapper:focus-within {
  background: var(--as-bg-elevated);
  border-color: var(--as-primary);
  box-shadow: 0 0 0 2px var(--as-primary-muted);
}

.chat-input {
  flex: 1;
  border: none;
  background: transparent;
  outline: none;
  font-size: 15px;
  color: var(--as-text);
  resize: none;
  overflow-y: auto;
  line-height: 1.5;
  min-height: 24px;
  max-height: 120px;
  padding: 0;
}

.chat-input::placeholder {
  color: var(--as-text-muted);
}

.chat-input::-webkit-scrollbar {
  width: 4px;
}

.chat-input::-webkit-scrollbar-thumb {
  background: var(--as-scrollbar-thumb);
  border-radius: 2px;
}

.send-btn {
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: all 0.2s ease;
  background: var(--as-primary);
  color: var(--as-primary-inverse);
}

.send-btn:hover {
  background: var(--as-primary-hover);
  transform: scale(1.05);
}

.send-btn:active {
  transform: scale(0.95);
}

/* --- Waiting states -------------------------------------------------------
   Three dots, in the two rhythms the app uses: a bounce while the assistant
   composes a reply, a pulse while a longer job runs.

   Both animations are prefixed. Their frozen-sheet names (`bounce`, `pulse`,
   `spin`) collided with Tailwind's own, which the generated layer also
   declares — and a duplicate `@keyframes` name is resolved per document, not
   per file, so which definition ran depended on link order.

   `.thinking-animation` shared both rhythms until the AI-chat modal was retired
   (docs/DEPRECATED_PAGES.md); the pulse survives because `.loading-indicator`
   on the intelligence graph is the other half of every rule it was in. */
.typing-indicator {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 10px;
}

.typing-indicator .dot {
  width: 8px;
  height: 8px;
  background-color: var(--as-gray-600);
  border-radius: 50%;
  animation: as-bounce 1.4s infinite ease-in-out both;
}

.typing-indicator .dot:nth-child(1) { animation-delay: 0s; }
.typing-indicator .dot:nth-child(2) { animation-delay: 0.16s; }
.typing-indicator .dot:nth-child(3) { animation-delay: 0.32s; }

@keyframes as-bounce {
  0%, 80%, 100% { transform: scale(0); }
  40% { transform: scale(1); }
}

.loading-indicator span.dot {
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-right: 2px;
  border-radius: 50%;
  background-color: var(--as-text-muted);
  animation: as-pulse 1.5s infinite ease-in-out;
}

.loading-indicator span.dot:nth-child(2) { animation-delay: 0.3s; }
.loading-indicator span.dot:nth-child(3) { animation-delay: 0.6s; }

@keyframes as-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

/* --------------------------------------------------------------------------
   8.10 Agent flows, runs and services
   --------------------------------------------------------------------------
   The workflow canvas, the agent flow editor, the run history and run detail,
   the service metrics stream, the agent settings page and the four dialogs
   those surfaces open. Separate from 8.2, which is the agents INDEX and the
   workspace detail page and holds itself to a stricter rule: every selector
   there names an element of one of those two pages. These are twenty views'
   worth of surface, and the vocabulary is `flow-*`, `wf-*` and `svc-*`.
   -------------------------------------------------------------------------- */

/* --- The agent-flow editor surface ----------------------------------------
   Shared by `agents/flow.blade.php` and `agents/workflow-canvas.blade.php`,
   which used to include a `partials/agents/_flow-editor-styles` partial whose
   only content was a `<style>` block. The partial is gone; the two pages that
   included it now get these rules from here.

   The `--wfe-*` layer stays: the canvas is a diagram surface whose accents
   (edge indigo, port fill, the amber search highlight) have no platform
   equivalent — `--as-primary` is the app's blue and `--as-info` its purple,
   and repainting the canvas in either would make an edge read as a link. What
   DOES map is the semantics: a true branch is the success colour and a false
   branch the danger colour, so those two read the platform tokens. */
:root {
  --wfe-port-default: #6366f1;
  --wfe-port-hover: #4f46e5;
  --wfe-edge-default: #6366f1;
  --wfe-edge-true: var(--as-success);
  --wfe-edge-false: var(--as-danger);
  --wfe-node-selected: var(--as-primary);
  --wfe-search-match: rgba(250, 204, 21, 0.45);
}

.flow-pane {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background: var(--as-bg-elevated);
  border-radius: 0.5rem;
  box-shadow: var(--as-shadow-hairline);
}

.flow-pane-header {
  padding: 0.625rem 0.875rem;
  border-bottom: 1px solid var(--as-gray-200);
  font-weight: 500;
  font-size: 0.875rem;
  color: var(--as-gray-700);
}

.flow-pane-body {
  flex: 1;
  padding: 0.5rem;
  overflow: auto;
}

.flow-palette-group-label {
  margin: 0.5rem 0 0.25rem;
  padding-left: 0.25rem;
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--as-gray-500);
}

.flow-palette-group-label:first-child {
  margin-top: 0;
}

.flow-palette-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.625rem;
  margin-bottom: 0.375rem;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 0.375rem;
  cursor: grab;
  font-size: 0.8125rem;
}

.flow-palette-item:hover {
  background: var(--as-primary-light);
  border-color: var(--as-primary-muted);
}

/* The node-type palette: twenty hues across the catalogue's twenty-eight step
   types (twenty-six in the palette plus the two internal ones the agent-flow
   canvas can still hold). They encode what a step IS — a trigger, an LLM call, a sink — not a
   theme role, so they stay literal for the same reason `.ctype-pill`'s twelve
   tints do (section 6): if they read the semantic tokens, "this is a condition
   node" and "this failed" would be the same red. The palette item's left border
   and the node's top border are the same list twice so an item dragged onto the
   canvas keeps its colour. `App\Services\Agents\NodeCatalog` is the source of
   truth for the type list; a type added there needs a line in both. */
.flow-palette-item[data-type='trigger_manual'] { border-left: 3px solid #6366f1; }
.flow-palette-item[data-type='trigger_schedule'] { border-left: 3px solid #16a34a; }
.flow-palette-item[data-type='trigger_webhook'] { border-left: 3px solid #d97706; }
.flow-palette-item[data-type='trigger_chat'] { border-left: 3px solid #0d9488; }
.flow-palette-item[data-type='datasource_view'] { border-left: 3px solid #0891b2; }
.flow-palette-item[data-type='datasource_object'] { border-left: 3px solid #0891b2; }
.flow-palette-item[data-type='input'] { border-left: 3px solid #22c55e; }
.flow-palette-item[data-type='output'] { border-left: 3px solid #3b82f6; }
.flow-palette-item[data-type='llm'] { border-left: 3px solid #8b5cf6; }
.flow-palette-item[data-type='tool_call'] { border-left: 3px solid #f59e0b; }
.flow-palette-item[data-type='rag_retrieve'] { border-left: 3px solid #06b6d4; }
.flow-palette-item[data-type='skill'] { border-left: 3px solid #ec4899; }
.flow-palette-item[data-type='chat_prompt'] { border-left: 3px solid #7c3aed; }
.flow-palette-item[data-type='agent_invoke'] { border-left: 3px solid #0ea5e9; }
.flow-palette-item[data-type='condition'] { border-left: 3px solid #ef4444; }
.flow-palette-item[data-type='join'] { border-left: 3px solid #6b7280; }
.flow-palette-item[data-type='transform_filter'] { border-left: 3px solid #facc15; }
.flow-palette-item[data-type='transform_format'] { border-left: 3px solid #facc15; }
.flow-palette-item[data-type='action'] { border-left: 3px solid #f59e0b; }
.flow-palette-item[data-type='subagent'] { border-left: 3px solid #0ea5e9; }
.flow-palette-item[data-type='delay'] { border-left: 3px solid #a3a3a3; }
.flow-palette-item[data-type='notify'] { border-left: 3px solid #10b981; }
.flow-palette-item[data-type='human_approval'] { border-left: 3px solid #dc2626; }
.flow-palette-item[data-type='sink_artifact'] { border-left: 3px solid #2563eb; }
.flow-palette-item[data-type='artifact_generate'] { border-left: 3px solid #2563eb; }
.flow-palette-item[data-type='sink_chat_response'] { border-left: 3px solid #2563eb; }
.flow-palette-item[data-type='sink_object_update'] { border-left: 3px solid #2563eb; }
.flow-palette-item[data-type='service_call'] { border-left: 3px solid #6366f1; }

/* The canvas. The wrapper is `overflow: hidden` so the pan transform is not
   fought by native scrolling; the inner surface is a fixed 2400x1600 plane
   translated and scaled from JS. */
#flow-canvas-wrapper {
  position: relative;
  flex: 1;
  overflow: hidden;
  background-color: var(--as-bg);
  background-image: radial-gradient(circle, var(--as-gray-200) 1px, transparent 1px);
  background-size: 16px 16px;
}

#flow-canvas {
  position: relative;
  width: 2400px;
  height: 1600px;
}

#flow-edges {
  position: absolute;
  inset: 0;
  width: 2400px;
  height: 1600px;
  pointer-events: none;
}

.flow-node {
  position: absolute;
  width: 180px;
  padding: 0.625rem 0.75rem;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-300);
  border-radius: 0.5rem;
  box-shadow: var(--as-shadow-hairline);
  cursor: move;
  font-size: 0.875rem;
  user-select: none;
}

.flow-node.is-selected {
  border-color: var(--wfe-node-selected);
  box-shadow: 0 0 0 2px var(--as-primary-muted);
}

.flow-node[data-type='trigger_manual'] { border-top: 3px solid #6366f1; }
.flow-node[data-type='trigger_schedule'] { border-top: 3px solid #16a34a; }
.flow-node[data-type='trigger_webhook'] { border-top: 3px solid #d97706; }
.flow-node[data-type='trigger_chat'] { border-top: 3px solid #0d9488; }
.flow-node[data-type='datasource_view'] { border-top: 3px solid #0891b2; }
.flow-node[data-type='datasource_object'] { border-top: 3px solid #0891b2; }
.flow-node[data-type='input'] { border-top: 3px solid #22c55e; }
.flow-node[data-type='output'] { border-top: 3px solid #3b82f6; }
.flow-node[data-type='llm'] { border-top: 3px solid #8b5cf6; }
.flow-node[data-type='tool_call'] { border-top: 3px solid #f59e0b; }
.flow-node[data-type='rag_retrieve'] { border-top: 3px solid #06b6d4; }
.flow-node[data-type='skill'] { border-top: 3px solid #ec4899; }
.flow-node[data-type='chat_prompt'] { border-top: 3px solid #7c3aed; }
.flow-node[data-type='agent_invoke'] { border-top: 3px solid #0ea5e9; }
.flow-node[data-type='condition'] { border-top: 3px solid #ef4444; }
.flow-node[data-type='join'] { border-top: 3px solid #6b7280; }
.flow-node[data-type='transform_filter'] { border-top: 3px solid #facc15; }
.flow-node[data-type='transform_format'] { border-top: 3px solid #facc15; }
.flow-node[data-type='action'] { border-top: 3px solid #f59e0b; }
.flow-node[data-type='subagent'] { border-top: 3px solid #0ea5e9; }
.flow-node[data-type='delay'] { border-top: 3px solid #a3a3a3; }
.flow-node[data-type='notify'] { border-top: 3px solid #10b981; }
.flow-node[data-type='human_approval'] { border-top: 3px solid #dc2626; }
.flow-node[data-type='sink_artifact'] { border-top: 3px solid #2563eb; }
.flow-node[data-type='artifact_generate'] { border-top: 3px solid #2563eb; }
.flow-node[data-type='sink_chat_response'] { border-top: 3px solid #2563eb; }
.flow-node[data-type='sink_object_update'] { border-top: 3px solid #2563eb; }
.flow-node[data-type='service_call'] { border-top: 3px solid #6366f1; }

.flow-node .title {
  font-weight: 500;
  color: var(--as-gray-900);
}

.flow-node .label {
  margin-top: 0.125rem;
  font-size: 0.75rem;
  color: var(--as-gray-600);
}

.flow-node .port {
  position: absolute;
  left: 50%;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--as-bg-elevated);
  border: 2px solid var(--wfe-port-default);
  transform: translateX(-50%);
  cursor: crosshair;
}

.flow-node .port.in { top: -7px; }
.flow-node .port.out { bottom: -7px; }
.flow-node .port:hover { background: var(--wfe-port-hover); }

/* A condition node has TWO output ports — true on the left, false on the
   right. `from_port` survives onto the edge so the serializer can build
   `branch_ports`. */
.flow-node[data-type='condition'] .port.out.true {
  left: 33%;
  border-color: var(--wfe-edge-true);
  transform: translateX(-50%);
}

.flow-node[data-type='condition'] .port.out.true:hover {
  background: var(--wfe-edge-true);
}

.flow-node[data-type='condition'] .port.out.false {
  left: 67%;
  border-color: var(--wfe-edge-false);
  transform: translateX(-50%);
}

.flow-node[data-type='condition'] .port.out.false:hover {
  background: var(--wfe-edge-false);
}

.flow-node[data-type='condition'] .port.out.true::after,
.flow-node[data-type='condition'] .port.out.false::after {
  content: '';
  position: absolute;
  top: 14px;
  left: 50%;
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  transform: translateX(-50%);
  pointer-events: none;
}

.flow-node[data-type='condition'] .port.out.true::after {
  content: 'T';
  color: var(--wfe-edge-true);
}

.flow-node[data-type='condition'] .port.out.false::after {
  content: 'F';
  color: var(--wfe-edge-false);
}

/* The source port of an in-flight connection, filled so the user can see where
   their pending edge starts.

   It has to beat the condition node's per-branch colours above, which are
   `.flow-node[data-type='condition'] .port.out.true` — one class more specific
   than a plain `.port.is-pending`. `[data-type]` brings this rule level with
   them and being below them settles the tie, which is what the style guide asks
   for instead of an `!important` (§1.1.1). `is-pending` only ever lands on an
   `out` port: all three branches of `applyPendingVisual()` select `.port.out`. */
.flow-node[data-type] .port.out.is-pending,
.flow-node[data-type] .port.out.is-pending:hover {
  background: var(--as-gray-900);
  border-color: var(--as-gray-900);
}

/* The inspector. `flow-fields.js` emits platform classes (`.input`,
   `.textarea`, `.select`, `.switch`) in a label-above-control row; this is
   the glue — vertical rhythm between rows, a min-height so the prompt editors
   are not one line, and the read-only `<code>` treatment `readonlyCode` uses
   for a webhook URL. Monospace is opt-in through `font-mono`. */
#flow-inspector > .w-full + .w-full,
#flow-inspector > .w-full + div.text-xs,
#flow-inspector > div.text-xs + .w-full {
  margin-top: 0.875rem;
}

#flow-inspector textarea.textarea {
  min-height: 80px;
  resize: vertical;
}

#flow-inspector textarea.textarea.font-mono,
#flow-inspector input.input.font-mono {
  font-family: ui-monospace, sfmono-regular, monospace;
}

.flow-inspector-code {
  display: block;
  padding: 0.375rem 0.5rem;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-300);
  border-radius: 0.375rem;
  font-family: ui-monospace, sfmono-regular, monospace;
  word-break: break-all;
}

/* The toolbar. Deliberately NOT a `.btn-group`: that family strips the inner
   radius to fuse buttons together, which beside our gap reads as spaced
   buttons with flat inner edges. Each button keeps its full radius here.

   The surface half (padding, background, rule) is the agent-flow page's — its
   toolbar sits directly on the page with no card around it. The canvas page
   restates all three at higher specificity below, because there the toolbar is
   inside `panel-main` and the panel already provides the card chrome. */
.flow-toolbar {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.75rem;
  background: var(--as-bg-elevated);
  border-bottom: 1px solid var(--as-gray-200);
}

.flow-toolbar-actions {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-left: auto;
}

.flow-zoom-controls,
.flow-canvas-mode-controls {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-left: 0.5rem;
  padding-left: 0.5rem;
  border-left: 1px solid var(--as-gray-200);
}

.flow-zoom-controls .btn,
.flow-canvas-mode-controls .btn {
  min-width: 28px;
  padding: 0.25rem 0.5rem;
}

#flow-zoom-level {
  min-width: 42px;
  font-size: 0.75rem;
  color: var(--as-gray-600);
  text-align: center;
  user-select: none;
}

.flow-node.search-match {
  box-shadow: 0 0 0 3px var(--wfe-search-match), var(--as-shadow-hairline);
}

/* The mini-map, bottom-right inside the canvas wrapper: a thumbnail of every
   node plus the viewport rectangle. */
#flow-minimap {
  position: absolute;
  right: 12px;
  bottom: 12px;
  z-index: 100;
  width: 200px;
  height: 120px;
  overflow: hidden;
  background: color-mix(in srgb, var(--as-bg-elevated) 92%, transparent);
  border: 1px solid var(--as-gray-300);
  border-radius: 4px;
  box-shadow: var(--as-shadow-raised);
  cursor: pointer;
  user-select: none;
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

#flow-minimap-viewport {
  position: absolute;
  border: 1.5px solid var(--wfe-port-default);
  background: color-mix(in srgb, var(--wfe-port-default) 8%, transparent);
  pointer-events: none;
}

.flow-mm-node {
  position: absolute;
  border-radius: 1px;
  background: var(--wfe-port-default);
  opacity: 0.6;
  pointer-events: none;
}

.flow-mm-node.is-selected {
  background: var(--wfe-node-selected);
  opacity: 0.9;
}

/* The rubber-band multi-select rectangle, a child of `#flow-canvas` so it is
   in canvas coordinate space. */
#flow-rubberband {
  position: absolute;
  z-index: 1000;
  display: none;
  border: 1.5px dashed var(--wfe-port-default);
  background: color-mix(in srgb, var(--wfe-port-default) 6%, transparent);
  pointer-events: none;
}

/* --- The agent flow page (`agents/flow.blade.php`) ------------------------
   A 220px palette column; everything else is the shared surface above. */
.flow-editor {
  display: grid;
  grid-template-columns: 220px 1fr 340px;
  gap: 1rem;
  height: var(--as-content-height);
  min-height: 520px;
}

@media (max-width: 1024px) {
  .flow-editor {
    grid-template-columns: 1fr;
    grid-auto-rows: min-content;
    height: auto;
  }
}

/* --- The workflow canvas page (`agents/workflow-canvas.blade.php`) --------
   Layout is the platform panel system (STYLE_GUIDE.md section 3); everything
   above owns the pane, palette, canvas, edge and node visuals. These rules
   only patch the panel containers for what the canvas page needs — a toolbar
   pinned to the top of `panel-main`, several header/body pairs stacked in
   `panel-right` — inside the panel grid.

   The height is hard-clamped to the content area. The base
   `.panel-container` md+ rule only sets a `min-height`; with no maximum, a
   palette taller than the grid row pushes the container past the viewport,
   which hides the toolbar above the fold and puts palette items at
   off-screen y-coordinates (which breaks the Playwright drag-and-drop). */
.panel-container.grid-12[data-flow-editor] {
  height: var(--as-content-height);
  min-height: 520px;
}

/* `.panel-left--scrollable` sets `display: flex; flex-direction: column` but
   not `min-height: 0`, without which the palette's `.flow-pane-body`
   (`flex: 1; overflow: auto`) cannot shrink below its content height — the
   panel outgrows the grid row and the bottom of the palette is only reachable
   through the OUTER page scroll. */
.panel-container.grid-12[data-flow-editor] .panel-left {
  min-height: 0;
  overflow: hidden;
}

/* Both `panel-main` (canvas) and `panel-right` (the inspector stack) need to
   be flex columns so their header/body/wrapper children can `flex: 1` to fill
   vertically. `panel-main` has `overflow: hidden` natively (section 5.5); it
   is restated here to also cover `panel-right`, which does not. */
.wf-panel-canvas,
.wf-panel-right {
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

/* `panel-main` wraps toolbar + canvas wrapper; the canvas fills the height
   below the toolbar. Pan and zoom happen inside the wrapper through a JS
   transform, so overflow stays hidden. */
.wf-panel-canvas > #flow-canvas-wrapper {
  flex: 1;
  min-height: 0;
}

/* The toolbar lives inside `panel-main` (it was a floating card above the
   editor grid), so it drops the elevation and the bottom margin the panel
   already provides. It stays on ONE row at every width: without the explicit
   `flex-wrap: nowrap` the Run and Cancel buttons wrapped onto a second row
   and disappeared below the visible area. Horizontal scroll keeps them
   reachable on a narrow viewport instead. */
.wf-panel-canvas > .flow-toolbar {
  flex-shrink: 0;
  flex-wrap: nowrap;
  gap: 0.25rem;
  margin-bottom: 0;
  padding: 0.5rem 0.625rem;
  overflow-x: auto;
  background: transparent;
  border-bottom: 1px solid var(--as-gray-200);
  border-radius: 0;
  box-shadow: none;
}

.wf-panel-canvas > .flow-toolbar > * {
  flex-shrink: 0;
}

/* Compact the toolbar so the Save/Run cluster fits inside the visible canvas
   at a 1380px viewport with the sidebar open. The "Workflow #N" caption is
   redundant — the page header above already carries the name and the status
   pill — so it goes first. */
.wf-panel-canvas > .flow-toolbar > span.text-sm {
  display: none;
}

.wf-panel-canvas > .flow-toolbar .btn.btn-sm {
  padding: 0.25rem 0.5rem;
}

.wf-panel-canvas > .flow-toolbar .btn.btn-sm.btn-icon {
  padding: 0.25rem;
}

/* The zoom read-out is the most expendable element when horizontal space is
   tight — the +/- buttons make the state obvious. It stays in the DOM so the
   `aria-live="polite"` announcement still fires on zoom. */
.wf-panel-canvas > .flow-toolbar #flow-zoom-level {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The run-history and metrics header rows share the `.flow-pane-header`
   chrome above; these only stop the headers collapsing as their bodies grow. */
.flow-run-history { flex-shrink: 0; }
.flow-run-history-body { flex: 1; padding: 0; overflow: auto; }
.flow-run-history-empty { padding: 0.625rem; }

.flow-run-row {
  display: grid;
  grid-template-columns: auto auto 1fr auto;
  gap: 0.5rem;
  align-items: center;
  padding: 0.375rem 0.625rem;
  border-bottom: 1px solid var(--as-gray-200);
  font-size: 0.75rem;
  cursor: pointer;
}

.flow-run-row:hover {
  background: var(--as-gray-100);
}

.flow-run-row__error {
  grid-column: 1 / -1;
  padding: 0.5rem 0.625rem;
  background: var(--as-danger-light);
  color: var(--as-danger-hover);
  font-family: ui-monospace, monospace;
  font-size: 0.6875rem;
  white-space: pre-wrap;
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

/* Dual-icon Run/Cancel: `flow.js` flips `data-running` on the button and CSS
   swaps the visible label and the colour, so the button reads as Cancel
   without rebuilding the markup. */
#flow-btn-run .flow-run-label-busy { display: none; }

#flow-btn-run[data-running='1'] {
  background-color: var(--as-danger);
  border-color: var(--as-danger);
}

#flow-btn-run[data-running='1'] .flow-run-label-idle { display: none; }
#flow-btn-run[data-running='1'] .flow-run-label-busy { display: inline; }

/* The narrow-viewport guard. The page script flips `data-narrow="1"` on the
   editor root below 1024px (unless the user dismissed the placeholder); the
   panel grid hides and the placeholder card takes its place. */
.panel-container.grid-12[data-flow-editor][data-narrow='1'] {
  display: block;
}

.panel-container.grid-12[data-flow-editor][data-narrow='1'] > .panel-left,
.panel-container.grid-12[data-flow-editor][data-narrow='1'] > .panel-main,
.panel-container.grid-12[data-flow-editor][data-narrow='1'] > .panel-right {
  display: none;
}

.wf-narrow-placeholder {
  display: none;
  margin: 1rem 0;
  padding: 2rem 1rem;
  text-align: center;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 0.75rem;
}

/* A sibling selector: the placeholder is never a child of `.panel-container`. */
.panel-container.grid-12[data-flow-editor][data-narrow='1'] + .wf-narrow-placeholder {
  display: block;
}

.wf-narrow-placeholder h3 {
  margin-bottom: 0.5rem;
  font-size: 1rem;
  color: var(--as-gray-900);
}

.wf-narrow-placeholder p {
  margin-bottom: 1rem;
  font-size: 0.85rem;
  color: var(--as-gray-700);
}

/* The "Building workflow…" overlay, shown by `workflow-chat.js` between the
   first SSE chunk and the post-apply `saveGraph` so there is a visible
   confirmation while the model streams and `arrange()` rewrites the graph. */
.wf-canvas-building-overlay {
  position: absolute;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--as-bg) 72%, transparent);
  animation: wf-canvas-building-fade 0.18s ease-out;
  pointer-events: all;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.wf-canvas-building-overlay.hidden { display: none; }

.wf-canvas-building-card {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.625rem 1rem;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 0.75rem;
  box-shadow: var(--as-shadow-raised);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--as-gray-900);
}

.wf-canvas-building-spinner {
  width: 20px;
  height: 20px;
  color: var(--as-primary);
  transform-origin: 50% 50%;
  animation: wf-canvas-building-spin 0.9s linear infinite;
}

@keyframes wf-canvas-building-spin {
  to { transform: rotate(360deg); }
}

@keyframes wf-canvas-building-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .wf-canvas-building-spinner,
  .wf-canvas-building-overlay {
    animation: none;
  }
}

/* The execution tab's trace table. */
#flow-trace-table .ft-row { cursor: default; }
#flow-trace-table .ft-row--clickable { cursor: pointer; }

#flow-trace-table .ft-row--clickable:hover td {
  background: var(--as-gray-100);
}

#flow-trace-table .ft-row td {
  padding: 0.4rem 0.5rem;
  border-bottom: 1px solid var(--as-gray-200);
  vertical-align: top;
}

#flow-trace-table .ft-row td.ft-num,
#flow-trace-table .ft-row td.ft-dur {
  color: var(--as-gray-600);
  font-variant-numeric: tabular-nums;
}

#flow-trace-table .ft-row td.ft-dur { text-align: right; }

#flow-trace-table .ft-row td.ft-detail {
  color: var(--as-gray-700);
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

@media (min-width: 768px) {
  /* The default `grid-12` split is 3/6/3 column spans, which on a 1280px
     canvas leaves the palette and inspector ~320px each and squeezes the
     editor to ~480px — narrow enough that the toolbar wraps, Run is pushed
     off-screen and the right-pane tab strip clips "Metrics" to "Metri". Fixed
     side panels with a flexible middle restore the intent: 200px / 1fr /
     340px gives a ~570px canvas at 1380px with the sidebar open. Below 768px
     the base `.panel-container.grid-12` rule already drops to one column. */
  .panel-container.grid-12[data-flow-editor],
  .panel-container.grid-12[data-flow-editor][data-panel-right-visible='true'] {
    grid-template-columns: 200px minmax(0, 1fr) 340px;
    gap: 12px;
  }

  .panel-container.grid-12[data-flow-editor] > .panel-left,
  .panel-container.grid-12[data-flow-editor][data-panel-right-visible='true'] > .panel-left {
    grid-column: 1;
  }

  .panel-container.grid-12[data-flow-editor] > .panel-main,
  .panel-container.grid-12[data-flow-editor][data-panel-right-visible='true'] > .panel-main {
    grid-column: 2;
  }

  .panel-container.grid-12[data-flow-editor] > .panel-right,
  .panel-container.grid-12[data-flow-editor][data-panel-right-visible='true'] > .panel-right {
    grid-column: 3;
  }
}

@media (max-width: 767px) {
  /* Below 768px `grid-12` collapses to one column and the three panels stack,
     so the hard content-height clamp would squash them. */
  .panel-container.grid-12[data-flow-editor] {
    height: auto;
    min-height: 0;
  }

  .wf-panel-canvas,
  .wf-panel-right {
    min-height: 400px;
  }
}

/* --- The run-history page (`agents/workflow-runs.blade.php`) --------------
   A filter bar, a table, and an expandable per-row detail panel. */
.wf-runs-filter {
  display: grid;
  grid-template-columns: repeat(4, 1fr) auto;
  gap: 0.5rem;
  align-items: end;
  margin-bottom: 0.875rem;
  padding: 0.875rem 1rem;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 0.5rem;
}

.wf-runs-filter label {
  display: block;
  margin-bottom: 0.25rem;
  font-size: 0.6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.025em;
  color: var(--as-gray-500);
}

.wf-runs-filter select,
.wf-runs-filter input {
  width: 100%;
  padding: 0.4375rem 0.625rem;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-300);
  border-radius: 0.375rem;
  font-size: 0.875rem;
}

.wf-runs-filter .wf-filter-actions {
  display: flex;
  align-items: end;
  gap: 0.5rem;
}

.wf-run-row {
  cursor: pointer;
  transition: background 0.1s;
}

.wf-run-row:hover,
.wf-run-row[aria-expanded='true'] {
  background: var(--as-gray-100);
}

.wf-run-row__chev {
  display: inline-block;
  width: 0.75rem;
  color: var(--as-gray-400);
  text-align: center;
  transition: transform 0.15s;
}

.wf-run-row[aria-expanded='true'] .wf-run-row__chev {
  transform: rotate(90deg);
}

.wf-run-detail {
  background: var(--as-gray-100);
  border-top: 1px solid var(--as-gray-200);
  border-bottom: 1px solid var(--as-gray-200);
}

.wf-run-detail__inner {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.875rem;
  padding: 0.875rem 1rem;
}

.wf-run-detail__block {
  min-width: 0;
}

.wf-run-detail__block h4 {
  margin-bottom: 0.375rem;
  font-size: 0.6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.025em;
  color: var(--as-gray-500);
}

.wf-run-detail__json {
  max-height: 320px;
  padding: 0.5rem 0.625rem;
  overflow: auto;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 0.25rem;
  font-family: ui-monospace, sfmono-regular, monospace;
  font-size: 0.6875rem;
  white-space: pre-wrap;
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

.wf-run-detail__error {
  padding: 0.625rem 0.75rem;
  background: var(--as-danger-light);
  border: 1px solid var(--as-danger-muted);
  border-radius: 0.25rem;
  color: var(--as-danger-hover);
  font-family: ui-monospace, sfmono-regular, monospace;
  font-size: 0.75rem;
  white-space: pre-wrap;
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

@media (max-width: 768px) {
  .wf-run-detail__inner {
    grid-template-columns: 1fr;
  }
}

/* --- The run-detail aside -------------------------------------------------
   `partials/agents/run-detail-aside.blade.php` is included with a `cssClass`
   so one markup partial can appear twice on a page. The two names it is
   called with (`flow-run-detail` on the canvas, `wf-run-detail` on the run
   list) are both listed here — the partial used to emit a `<style>` block
   with the class interpolated into every selector, which is what made it
   impossible to serve from a stylesheet. */
.flow-run-detail {
  margin-top: 1em;
  padding: 1em 1.2em;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 8px;
}

.flow-run-detail[hidden] { display: none; }

.flow-run-detail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6em;
  margin-bottom: 0.6em;
}

.flow-run-detail-head h4 {
  margin: 0;
  font-size: 0.9em;
  font-weight: 600;
  color: var(--as-gray-900);
}

.flow-run-detail-close {
  padding: 0.1em 0.4em;
  background: none;
  border: 0;
  border-radius: 4px;
  color: var(--as-gray-500);
  font-size: 1.1em;
  line-height: 1;
  cursor: pointer;
}

.flow-run-detail-close:hover {
  background: var(--as-gray-100);
  color: var(--as-gray-900);
}

.flow-run-detail section {
  margin-bottom: 0.6em;
}

.flow-run-detail h5 {
  margin: 0 0 0.25em;
  font-size: 0.78em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--as-gray-700);
}

.flow-run-detail-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6em;
  margin-bottom: 0.6em;
  padding-bottom: 0.6em;
  border-bottom: 1px solid var(--as-gray-200);
}

/* The payload dump keeps its console treatment on every theme — a terminal
   that inverts with the page reads as a bug, and the `--as-coal-*` ramp is
   the platform's fixed-dark surface for exactly this. */
.flow-run-detail-pre {
  max-height: 22em;
  margin: 0;
  padding: 0.7em 0.9em;
  overflow: auto;
  background: var(--as-coal-400);
  border-radius: 6px;
  color: var(--as-gray-200);
  font-family: ui-monospace, monospace;
  font-size: 0.78em;
  line-height: 1.45;
  white-space: pre-wrap;
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

.flow-run-detail-pre--err {
  background: var(--as-danger-light);
  border: 1px solid var(--as-danger-muted);
  color: var(--as-danger-hover);
}

/* --- The pin-to-project menu ---------------------------------------------- */
.pin-to-project-menu {
  display: flex;
  flex-direction: column;
  width: 260px;
  max-height: 24em;
  padding: 0.6em;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 8px;
  box-shadow: var(--as-shadow-raised);
}

.pin-to-project-menu.hidden { display: none; }

.pin-to-project-heading {
  margin-bottom: 0.4em;
  font-size: 0.78em;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--as-gray-600);
}

.pin-to-project-menu input[type='search'] {
  box-sizing: border-box;
  width: 100%;
  margin-bottom: 0.5em;
  padding: 0.3em 0.5em;
  border: 1px solid var(--as-gray-300);
  border-radius: 6px;
  font-size: 0.85em;
}

.pin-to-project-list {
  flex: 1 1 auto;
  overflow: auto;
}

.pin-to-project-row {
  display: block;
  width: 100%;
  padding: 0.45em 0.6em;
  background: transparent;
  border: 0;
  border-radius: 4px;
  color: var(--as-gray-900);
  font-size: 0.9em;
  text-align: left;
  cursor: pointer;
}

.pin-to-project-row:hover,
.pin-to-project-row:focus {
  background: var(--as-primary-light);
  outline: 0;
}

.pin-to-project-empty {
  padding: 0.5em 0.6em;
  font-size: 0.85em;
  color: var(--as-gray-600);
}

/* --- The viewer presence pill --------------------------------------------- */
.agent-viewer-pill[hidden] { display: none; }

.agent-viewer-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-right: 0.25rem;
  padding: 0.1rem 0.5rem;
  background: var(--as-primary-light);
  border: 1px solid var(--as-primary-muted);
  border-radius: 999px;
  color: var(--as-primary);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1.3;
}

.agent-viewer-pill-label {
  color: var(--as-gray-700);
  font-weight: 500;
}

.agent-viewer-pill-list {
  display: inline-flex;
  align-items: center;
}

.agent-viewer-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.1rem;
  height: 1.1rem;
  overflow: hidden;
  background: var(--as-gray-300);
  border: 1.5px solid var(--as-bg-elevated);
  border-radius: 50%;
  box-shadow: 0 0 0 1px var(--as-primary-muted);
  color: var(--as-gray-800);
  font-size: 0.55rem;
  font-weight: 600;
}

.agent-viewer-chip + .agent-viewer-chip {
  margin-left: -0.4rem;
}

.agent-viewer-chip img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.agent-viewer-chip.is-fallback {
  background: var(--as-info-muted);
  color: var(--as-info);
}

/* --- The content browser --------------------------------------------------- */
.agent-content-browser { margin: 0.6em 0; }

.content-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4em;
  margin-bottom: 0.4em;
}

.content-view-switch {
  display: inline-flex;
  gap: 0.2em;
}

.content-view-switch .btn.is-active {
  background: var(--as-primary-light);
  border-color: var(--as-primary-muted);
  color: var(--as-primary);
}

.content-row {
  display: flex;
  align-items: center;
  gap: 0.4em;
  padding: 0.45em 0.5em;
  border-bottom: 1px solid var(--as-gray-200);
}

.content-row:last-child { border-bottom: none; }

.content-name {
  flex: 1;
  font-weight: 500;
}

.content-badge {
  padding: 0.1em 0.45em;
  border-radius: 4px;
  font-size: 0.7rem;
}

.badge-generated,
.content-badge.badge-generated {
  background: var(--as-primary-light);
  color: var(--as-primary-hover);
}

.badge-uploaded,
.content-badge.badge-uploaded {
  background: var(--as-gray-100);
  color: var(--as-gray-800);
}

.content-kind {
  max-width: 180px;
  overflow: hidden;
  color: var(--as-gray-600);
  font-size: 0.72rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.content-date {
  color: var(--as-gray-500);
  font-size: 0.72rem;
}

.content-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}

.content-table th,
.content-table td {
  padding: 0.35em 0.5em;
  border-bottom: 1px solid var(--as-gray-200);
  text-align: left;
}

.content-table th {
  background: var(--as-gray-100);
  cursor: pointer;
  user-select: none;
}

/* --- The workflow metrics panel -------------------------------------------- */
.flow-metrics-windows {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.flow-metrics-window {
  padding: 0.5rem;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 6px;
}

.flow-metrics-window__label,
.flow-metrics-section__title {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--as-gray-600);
}

.flow-metrics-window__value {
  margin-top: 0.125rem;
  font-size: 1.1rem;
  font-weight: 600;
  color: var(--as-gray-900);
}

.flow-metrics-window__sub {
  margin-top: 0.125rem;
  font-size: 0.7rem;
  color: var(--as-gray-600);
}

.flow-metrics-section { margin-bottom: 0.75rem; }
.flow-metrics-section__title { margin-bottom: 0.25rem; }

.flow-metrics-row {
  display: flex;
  justify-content: space-between;
  padding: 0.125rem 0;
  border-bottom: 1px dashed var(--as-gray-200);
}

.flow-metrics-row:last-child { border-bottom: none; }
.flow-metrics-row__label { color: var(--as-gray-700); }

.flow-metrics-row__count {
  color: var(--as-gray-900);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* --- The chat-applies audit log (`agents/workflow-chat-applies`) ----------- */
.wfca-wrap {
  max-width: 1200px;
  margin: 0 auto;
  padding: 1.25rem 1.5rem;
}

.wfca-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem;
}

.wfca-heading h1 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
}

.wfca-back {
  color: var(--as-primary);
  font-size: 0.875rem;
  text-decoration: none;
}

.wfca-back:hover { text-decoration: underline; }

.wfca-filters {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.5rem;
  align-items: end;
  margin-bottom: 0.875rem;
  padding: 0.875rem 1rem;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 6px;
}

.wfca-filters label {
  display: block;
  margin-bottom: 0.25rem;
  color: var(--as-gray-600);
  font-size: 0.75rem;
}

.wfca-filters input,
.wfca-filters select {
  width: 100%;
  padding: 0.375rem 0.625rem;
  border: 1px solid var(--as-gray-300);
  border-radius: 4px;
  font-size: 0.875rem;
}

.wfca-filters .wfca-actions {
  display: flex;
  gap: 0.5rem;
}

.wfca-filters button {
  padding: 0.4rem 0.875rem;
  background: var(--as-primary);
  border: 1px solid var(--as-primary);
  border-radius: 4px;
  color: var(--as-primary-inverse);
  font-size: 0.875rem;
  cursor: pointer;
}

.wfca-filters a.wfca-clear {
  padding: 0.4rem 0.875rem;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-300);
  border-radius: 4px;
  color: var(--as-gray-700);
  font-size: 0.875rem;
  text-decoration: none;
}

.wfca-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.wfca-table th,
.wfca-table td {
  padding: 0.625rem 0.75rem;
  border-bottom: 1px solid var(--as-gray-200);
  text-align: left;
  vertical-align: top;
}

.wfca-table th {
  background: var(--as-gray-100);
  color: var(--as-gray-700);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.wfca-table tr:hover td { background: var(--as-gray-50); }

.wfca-msg {
  max-width: 420px;
  color: var(--as-gray-600);
  font-style: italic;
  white-space: pre-wrap;
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

.wfca-pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 1rem;
  color: var(--as-gray-600);
  font-size: 0.875rem;
}

.wfca-pager .wfca-pager-links a {
  padding: 0.25rem 0.5rem;
  color: var(--as-primary);
  text-decoration: none;
}

.wfca-pager .wfca-pager-links a:hover { text-decoration: underline; }

.wfca-pager .wfca-pager-current {
  padding: 0.25rem 0.5rem;
  color: var(--as-gray-900);
  font-weight: 600;
}

.wfca-empty {
  padding: 2rem;
  color: var(--as-gray-600);
  text-align: center;
}

.wfca-params {
  max-height: 5em;
  overflow: hidden;
  color: var(--as-gray-700);
  font-family: ui-monospace, monospace;
  font-size: 0.7rem;
}

/* --- The conversation viewer ---------------------------------------------- */
.conversation-viewer {
  max-width: 900px;
  margin: 0 auto;
  padding: 1.25rem;
}

.conversation-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1em;
}

.conversation-meta {
  color: var(--as-gray-500);
  font-size: 0.85rem;
}

.conversation-thread {
  padding: 1em 1.2em;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 8px;
}

.conv-msg {
  max-width: 85%;
  margin: 1em 0;
}

.conv-msg-user {
  margin-left: auto;
  padding: 0.7em 1em;
  background: var(--as-primary-light);
  border-radius: 0.75rem 0.75rem 0.25rem 0.75rem;
}

.conv-msg-assistant {
  margin-right: auto;
  padding: 0.7em 1em;
  background: var(--as-gray-100);
  border-radius: 0.75rem 0.75rem 0.75rem 0.25rem;
}

.conv-msg-meta {
  margin-bottom: 0.2em;
  color: var(--as-gray-400);
  font-size: 0.72rem;
}

/* A long thread pages from the newest message backwards. */
.conv-load-earlier {
  display: block;
  margin: 0 auto 0.75em;
  padding: 0.25em 1em;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 999px;
  color: var(--as-primary);
  font-size: 0.78rem;
  cursor: pointer;
}

.conv-load-earlier:hover { background: var(--as-gray-200); }

.conv-artifacts {
  margin-top: 1.5em;
  padding: 0.8em 1em;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 8px;
}

.conv-artifact-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.4em 0;
  border-bottom: 1px solid var(--as-gray-200);
}

.conv-artifact-row:last-child { border-bottom: none; }

/* --- Service metrics ------------------------------------------------------- */
.svc-metric-card {
  padding: 1rem 1.2rem;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 8px;
}

.svc-metric-card .lbl {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--as-gray-500);
}

.svc-metric-card .val {
  margin-top: 0.25rem;
  color: var(--as-gray-900);
  font-size: 1.5rem;
  font-weight: 600;
}

.svc-metric-card .sub {
  margin-top: 0.15rem;
  color: var(--as-gray-500);
  font-size: 0.75rem;
}

.svc-metric-card .svc-metric-unit {
  color: var(--as-gray-500);
  font-size: 0.65em;
  font-weight: 400;
}

.svc-stream {
  font-family: ui-monospace, menlo, monaco, monospace;
  font-size: 0.78rem;
}

.svc-stream .row {
  display: grid;
  grid-template-columns: 9rem 5rem 6rem 1fr;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.4rem 0.6rem;
  border-bottom: 1px solid var(--as-gray-200);
}

.svc-stream .row.error { background: var(--as-danger-light); }
.svc-stream .row.ok { background: var(--as-bg-elevated); }
.svc-stream .ts { color: var(--as-gray-600); }
.svc-stream .status { font-weight: 600; }
.svc-stream .status.ok { color: var(--as-success); }
.svc-stream .status.error { color: var(--as-danger); }
.svc-stream .status.pending { color: var(--as-warning); }

.svc-stream .summary {
  color: var(--as-gray-700);
  /* The view spelled this `word-break: break-word`, which the spec defines as
     exactly this declaration and which stylelint now rejects as deprecated. */
  overflow-wrap: anywhere;
}

.svc-stream .live-badge {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  margin-right: 0.4rem;
  background: var(--as-success);
  border-radius: 50%;
  vertical-align: middle;
}

.svc-stream-empty {
  padding: 1.5rem;
  color: var(--as-gray-500);
  font-size: 0.85rem;
  text-align: center;
}

/* --- The agent settings page ----------------------------------------------- */
.agent-settings-tabs {
  display: flex;
  gap: 0.25rem;
  margin-bottom: 1.5rem;
  border-bottom: 1px solid var(--as-gray-200);
}

.agent-settings-tabs button {
  margin-bottom: -1px;
  padding: 0.625rem 1rem;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  color: var(--as-gray-500);
  font-size: 0.875rem;
  cursor: pointer;
}

.agent-settings-tabs button:hover { color: var(--as-gray-700); }

.agent-settings-tabs button.is-active {
  border-bottom-color: var(--as-primary);
  color: var(--as-primary);
  font-weight: 500;
}

.agent-settings-panel.hidden { display: none; }

.agent-setting-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 0;
  border-bottom: 1px solid var(--as-gray-200);
}

.agent-setting-item:last-child { border-bottom: none; }

.agent-setting-label {
  color: var(--as-gray-900);
  font-size: 0.875rem;
}

.agent-setting-description {
  margin-top: 0.125rem;
  color: var(--as-gray-500);
  font-size: 0.75rem;
}

.agent-setting-saving { opacity: 0.5; }

.agent-upload-area {
  padding: 2rem;
  border: 2px dashed var(--as-gray-300);
  border-radius: 0.5rem;
  text-align: center;
  cursor: pointer;
}

.agent-upload-area:hover { border-color: var(--as-primary); }

.agent-upload-area.dragover {
  background: var(--as-primary-light);
  border-color: var(--as-primary);
}

/* --- Approvals, models, services and the list pages ------------------------ */
.approvals-table th,
.approvals-table td {
  padding: 0.55rem 0.6rem;
}

.approvals-empty {
  padding: 2rem;
  color: var(--as-gray-500);
  font-size: 0.9rem;
  text-align: center;
}

.agent-model-row.updating { opacity: 0.5; }

.agent-model-table th,
.agent-model-table td {
  padding: 0.5rem 0.75rem;
  font-size: 0.875rem;
  text-align: left;
}

.agent-model-table tr { border-bottom: 1px solid var(--as-gray-200); }

.agent-model-table thead th {
  color: var(--as-gray-500);
  font-size: 0.75rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.service-card { transition: box-shadow 0.15s; }
.service-card:hover { box-shadow: var(--as-shadow-raised); }

/* The grid/list switch on the projects and workspaces lists. `agents/index`
   already reads `.is-active` from section 6; these two pages carry their own
   ids, so the ids are what the rule keys on. */
#projects_view_grid.is-active,
#projects_view_list.is-active,
#workspaces_view_grid.is-active,
#workspaces_view_list.is-active {
  background: var(--as-primary-light);
  border-color: var(--as-primary-muted);
  color: var(--as-primary);
}

#projects_grid[data-view-mode='list'] .project-card,
#workspaces_grid[data-view-mode='list'] .workspace-card {
  padding: 0.75rem 1rem;
}

#projects_grid[data-view-mode='list'] .project-card > div:first-child,
#workspaces_grid[data-view-mode='list'] .workspace-card > div:first-child {
  margin-bottom: 0.25rem;
}

/* --- The agent, workflow and service dialogs -------------------------------
   Each dialog's tab strip marks its current pane; `agentmodal` predates the
   `aria-selected` convention the other two use, so it keys on `.active`. */
#agentmodal .ag-tab-btn,
#workflowmodal .wf-tab-btn,
#servicemodal .svc-tab-btn,
#brandkitmodal .bk-tab-btn,
#brandkitmodal .bk-sub-tab-btn {
  color: var(--as-gray-700);
}

#agentmodal .ag-tab-btn.active,
#brandkitmodal .bk-tab-btn.active,
#brandkitmodal .bk-sub-tab-btn.active,
#workflowmodal .wf-tab-btn[aria-selected='true'],
#servicemodal .svc-tab-btn[aria-selected='true'] {
  background: var(--as-bg-elevated);
  box-shadow: var(--as-shadow-hairline);
  color: var(--as-gray-900);
}

#agentmodal .ag-accent-chip.is-selected,
#workflowmodal .ag-accent-chip.is-selected {
  box-shadow: 0 0 0 2px var(--as-bg-elevated), 0 0 0 4px currentcolor;
  transform: scale(1.15);
}

#agentmodal #ag-knowledge-dropzone.dragover,
#brandkitmodal #bk-dropzone.dragover,
#brandkitmodal .bk-logo-preview.bk-logo-dragover {
  background: color-mix(in srgb, var(--as-primary) 8%, transparent);
  border-color: var(--as-primary);
}

#ag-prompt-input { line-height: 1.4; }

#servicemodal .svc-cat-card {
  padding: 8px 10px;
  border: 1px solid var(--as-gray-200);
  border-radius: 8px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s, background-color 0.15s;
}

#servicemodal .svc-cat-card[aria-pressed='true'] {
  background: var(--as-info-muted);
  border-color: var(--as-info);
  color: var(--as-info-hover);
}

#servicemodal .svc-cat-card:hover { border-color: var(--as-info-muted); }

#brandkitmodal .bk-preset.selected {
  border-color: var(--as-primary);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--as-primary) 25%, transparent);
}

#brandkitmodal .bk-preset.selected .bk-preset-check { display: inline-flex; }

#brandkitmodal .bk-color-preview {
  width: 100%;
  height: 4.5rem;
  margin-bottom: 0.75rem;
  border-radius: 0.75rem;
  box-shadow: inset 0 0 0 1px var(--as-gray-200);
  cursor: pointer;
}

/* The "no colour set" swatch is a checkerboard, which is a texture rather
   than a surface — it keeps its two ramp steps so the pattern stays visible
   on every theme. */
#brandkitmodal .bk-color-empty {
  background: repeating-linear-gradient(
    45deg,
    var(--as-gray-100),
    var(--as-gray-100) 4px,
    var(--as-gray-200) 4px,
    var(--as-gray-200) 8px
  );
  cursor: pointer;
}

#brandkitmodal .bk-logo-preview.bk-logo-has-image { border-style: solid; }

#brandkitmodal .bk-logo-preview:focus-visible {
  outline: 2px solid var(--as-primary);
  outline-offset: 2px;
}

/* --- The data-sharing block dialog ----------------------------------------
   Raised by `data-sharing-banner.js` when a request is refused because the
   team has data sharing off. The script used to build this rule set as a
   string and inject it into `<head>` the first time the dialog opened. */
#agent_data_sharing_modal {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
}

#agent_data_sharing_modal .agent-ds-overlay {
  position: absolute;
  inset: 0;
  background: var(--as-coal-clarity);
}

#agent_data_sharing_modal .agent-ds-panel {
  position: relative;
  width: 90%;
  max-width: 520px;
  padding: 1.3em 1.5em;
  background: var(--as-bg-elevated);
  border-radius: 10px;
  box-shadow: var(--as-shadow);
}

#agent_data_sharing_modal h3 {
  margin: 0 0 0.4em;
  color: var(--as-gray-900);
}

#agent_data_sharing_modal .agent-ds-source {
  margin-top: 0.3em;
  color: var(--as-gray-600);
  font-size: 0.8rem;
}

#agent_data_sharing_modal footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.6em;
  margin-top: 0.8em;
}

#agent_data_sharing_modal .agent-ds-hint {
  color: var(--as-gray-600);
  font-size: 0.85rem;
}

/* The run-workflow dialog's console. It sits on the same fixed-dark surface
   as the run-detail payload dump, so the log levels are the light tints of
   the semantic tokens rather than the tokens themselves. */
#rwm-output-log .rwm-log { color: var(--as-gray-300); }
#rwm-output-log .rwm-log--start { color: #93c5fd; }
#rwm-output-log .rwm-log--step { color: var(--as-gray-500); }
#rwm-output-log .rwm-log--ok { color: #6ee7b7; }
#rwm-output-log .rwm-log--skip { color: #fcd34d; }
#rwm-output-log .rwm-log--fail { color: #fca5a5; }
#rwm-output-log .rwm-log--error { color: #f87171; }
#rwm-output-log .rwm-log--finish-ok { color: #6ee7b7; font-weight: 600; }
#rwm-output-log .rwm-log--finish-fail { color: #f87171; font-weight: 600; }

/* --------------------------------------------------------------------------
   8.11 The object list surface
   --------------------------------------------------------------------------
   `partials/listtable`, the four `objectdetails_*` views, `similar` and
   `imports`. The table itself is the `.objecttable` family in section 6; these
   are the per-page arrangements around it.
   -------------------------------------------------------------------------- */

/* The actions column disappears when no row in the table has an action. Which
   rows have one is only known after the row loop has run, so `listtable`
   trails the table with a hidden marker and this keys off it — it used to
   emit a `<style>` block with the table's key interpolated into the selector,
   which is a rule that cannot live in a stylesheet. */
.card:has(> .objecttable-actions-empty) .actions-header,
.card:has(> .objecttable-actions-empty) .actions-cell {
  display: none;
}

/* Full-height list pages. `--as-content-height` is the token spelling of the
   `calc(100vh - header - 150px)` these five views each wrote out by hand.

   The modifier exists because the rules were unscoped `.panel-container`
   overrides in the page: in one stylesheet they would re-flow every panel
   page in the app, including the agents surfaces that size themselves. */
.panel-container--fill {
  max-height: none;
}

/* The narrower variant: the list/table pairs want the height floor without
   the grid-row and flex-column treatment the field list needs. */
@media (min-width: 768px) {
  .panel-container--fill .panel-left,
  .panel-container--fill .panel-main,
  .panel-container--min-fill .panel-left,
  .panel-container--min-fill .panel-main {
    height: auto;
    min-height: var(--as-content-height);
    max-height: none;
  }

  .panel-container--fill {
    grid-template-rows: minmax(var(--as-content-height), auto);
    grid-auto-rows: auto;
    align-items: start;
  }

  /* The list column scrolls internally rather than growing the page. */
  .panel-container--fill .panel-left {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .panel-container--fill .panel-left #list-content {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .panel-container--fill .panel-left #list-content > .list-items-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    will-change: transform;
  }

  /* The column's fixed furniture — the card header, the rule under it and the
     tab strip on the pattern page — must not absorb the space the scroller
     below it needs. (The field list has no tab strip; the rule is harmless
     there and `flex-shrink: 0` is what a header in a flex column wants
     anyway.) */
  .panel-container--fill .panel-left .card-header,
  .panel-container--fill .panel-left .border-b,
  .panel-container--fill .panel-left .pt-4 {
    flex-shrink: 0;
  }

  .panel-container--fill .panel-left .tab-panel {
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }

  .panel-container--fill .panel-left .tab-panel > div[style*="overflow-y"] {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
  }

  /* A flex column turns `display: none` into a fight with `display: flex`;
     the hidden tab has to stay hidden. */
  .panel-container--fill .panel-left .hidden {
    display: none;
  }
}

/* The instance rows in the list column. `.lsitem` is markup in seven views,
   only one of which ever had these rules, so the page scope stays. */
.object-list-detail .category-group {
  /* Keeps a collapse/expand from relaying out the whole scroller. */
  contain: layout style;
}

.object-list-detail .lsitem {
  padding: 1rem;
  border-bottom: 1px solid var(--as-gray-200);
  cursor: pointer;
  transition: background-color 0.15s ease-in-out;
}

.object-list-detail .lsitem:hover {
  background-color: var(--as-gray-100);
}

.object-list-detail .lsitem.active {
  background-color: var(--as-primary-light);
  border: 1px solid var(--as-primary-muted);
  border-radius: var(--as-radius);
}

.object-list-detail .delete-instance-btn:hover {
  color: var(--as-danger);
}

/* The similar-records page: its first column is the case id (right-aligned
   and one ramp step stronger) and its third is a percentage. */
.similar-records .objecttable thead th:nth-child(1) {
  padding-right: 1rem;
  text-align: right;
}

.similar-records .objecttable tbody td:nth-child(1),
.similar-records .objecttable tbody td:nth-child(1) a {
  color: var(--as-text);
  font-weight: 500;
}

.similar-records .objecttable thead th:nth-child(3),
.similar-records .objecttable tbody td:nth-child(3) {
  text-align: center;
}

/* The import lists: numeric columns right, status column centred. Both
   tables are keyed by id, so no page scope is needed. */
#datatable-active-imports th:nth-child(1),
#datatable-active-imports td:nth-child(1),
#datatable-active-imports th:nth-child(4),
#datatable-active-imports td:nth-child(4),
#datatable-recent-imports th:nth-child(1),
#datatable-recent-imports td:nth-child(1),
#datatable-recent-imports th:nth-child(4),
#datatable-recent-imports td:nth-child(4) {
  padding-right: 1rem;
  text-align: right;
}

#datatable-active-imports th:nth-child(3),
#datatable-active-imports td:nth-child(3),
#datatable-recent-imports th:nth-child(5),
#datatable-recent-imports td:nth-child(5) {
  text-align: center;
}

/* --------------------------------------------------------------------------
   8.12 Patterns and QC
   --------------------------------------------------------------------------
   `pattern.blade.php` and `qc.blade.php`. The two pages carry the same
   `.pattern-item` class with different tints, and `.pattern-item` is markup in
   a third view (`partials/ai-recommendations-tab`) that styles it from the
   utilities, so both rules stay behind their page scope.
   -------------------------------------------------------------------------- */
.pattern-detail .pattern-item.active {
  background-color: var(--as-primary-light);
  border: 1px solid var(--as-primary-muted);
  border-radius: var(--as-radius);
}

.pattern-detail .pattern-item .bg-primary-light {
  background-color: var(--as-primary-muted);
}

.qc-detail .pattern-item.active {
  background-color: var(--as-primary-light);
}

/* The sample-size slider. The platform range control is the primary blue;
   the QC page's sample size is an analysis control, so it reads the info
   purple to separate "how much am I sampling" from "save this". Every
   vendor pseudo-element has to be written out — a browser drops the whole
   selector list if one of them is unknown to it. */
.qc-detail #sampleSizeSlider::-webkit-slider-thumb {
  appearance: none;
  width: 16px;
  height: 16px;
  border: 0;
  border-radius: 50%;
  background: var(--as-info);
  cursor: pointer;
}

.qc-detail #sampleSizeSlider::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border: 0;
  border-radius: 50%;
  background: var(--as-info);
  cursor: pointer;
}

.qc-detail #sampleSizeSlider::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(
    to right,
    var(--as-info) 0%,
    var(--as-info) var(--slider-progress, 33%),
    var(--as-gray-200) var(--slider-progress, 33%),
    var(--as-gray-200) 100%
  );
}

.qc-detail #sampleSizeSlider::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: var(--as-gray-200);
}

.qc-detail #sampleSizeSlider::-moz-range-progress {
  height: 6px;
  border-radius: 3px;
  background: var(--as-info);
}

.qc-detail #sampleSizeSlider:disabled::-webkit-slider-thumb,
.qc-detail #sampleSizeSlider:disabled::-moz-range-thumb {
  background: var(--as-gray-500);
}

.qc-detail #sampleSizeSlider:focus::-webkit-slider-thumb {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--as-info-muted);
}

/* The scatter plot and its rubber-band selection. */
.qc-detail .qc-visual-container {
  position: relative;
  height: 100%;
}

.qc-detail .selection-box {
  position: absolute;
  z-index: 10;
  border: 2px dashed var(--as-primary);
  background-color: var(--as-primary-muted);
  pointer-events: none;
}

.qc-detail .ai-explanation {
  border-radius: 8px;
}

.qc-detail .card-body {
  padding: 1.5rem;
}

.qc-detail .chart-container {
  position: relative;
  width: 100%;
  height: 400px;
}

/* --------------------------------------------------------------------------
   8.13 The report builder
   --------------------------------------------------------------------------
   `reportbuilder.blade.php`. Everything is scoped to the page root: the ids
   are generic enough (`#aiProgressBar`, `.print-view`) that an unscoped rule
   would be a trap for the next page that picks the same name.
   -------------------------------------------------------------------------- */
.report-builder .nonedit,
.report-builder .pluginblock {
  display: inline-block;
  margin: 0;
  padding: 0;
}

.report-builder .print-view {
  display: none;
}

/* Read-only mode. The page used to emit `#toggleeditorbtn, #savechangesbtn {
   display: none }` inside an `@if($usingtemplate)`, i.e. a Blade conditional
   in the middle of a stylesheet; the condition is a class on the root now. */
.report-builder.is-template-readonly #toggleeditorbtn,
.report-builder.is-template-readonly #savechangesbtn {
  display: none;
}

.report-builder #aiReportPrompt {
  font-size: 14px;
  line-height: 1.5;
}

.report-builder #aiReportPrompt:focus {
  outline: none;
  border-color: var(--as-primary);
  box-shadow: 0 0 0 3px var(--as-primary-muted);
}

.report-builder #generateReportBtn {
  font-weight: 500;
  cursor: pointer;
}

.report-builder #generateReportBtn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

.report-builder #aiGenerationStatus {
  padding: 8px;
  border-radius: 4px;
  font-size: 13px;
}

.report-builder #aiGenerationStatus .text-blue-600 { color: var(--as-primary); }
.report-builder #aiGenerationStatus .text-green-600 { color: var(--as-success); }
.report-builder #aiGenerationStatus .text-red-600 { color: var(--as-danger); }

.report-builder .ai-expand-item {
  margin-bottom: 6px;
}

.report-builder .ai-expand-btn {
  padding: 4px 8px;
  font-size: 12px;
  cursor: pointer;
  transition: all 0.2s;
}

.report-builder .ai-expand-btn:hover:not(:disabled) {
  transform: translateX(2px);
}

.report-builder .ai-expand-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

/* `as-spin` rather than `spin`: the generated utilities layer declares its own
   `spin`, and a duplicate `@keyframes` name resolves per document. */
.report-builder .ai-expand-btn .spinner {
  display: inline-block;
  animation: as-spin 1s linear infinite;
}

#aiProgressModal {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 10dvh;
  background: var(--as-coal-clarity);
  backdrop-filter: blur(4px);
}

#aiProgressModal .ai-progress-content {
  width: 90%;
  max-width: 500px;
  background: var(--as-bg-elevated);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow);
}

#aiProgressModal .ai-progress-body {
  padding: 2rem;
}

#aiProgressModal #aiProgressMessage {
  min-height: 28px;
  animation: rb-message-fade-in 0.5s ease-in-out;
}

#aiProgressModal #aiProgressBar {
  transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}

#aiProgressModal svg.animate-spin {
  animation: rb-spin-pulse 1.5s linear infinite;
}

@keyframes rb-message-fade-in {
  from {
    opacity: 0;
    transform: translateY(-5px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes rb-spin-pulse {
  0% {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }

  100% {
    opacity: 1;
    transform: scale(1) rotate(360deg);
  }
}

/* View mode hides the authoring panels and shows one explanatory line in
   their place; edit mode is the inverse. `print.css` strips card padding in
   view mode, so the header padding is restated to stop the panel jumping as
   the two modes swap. */
.report-builder .panel-left .card-header,
.report-builder #objecttable .card-header {
  padding: 1rem;
}

.report-builder .panel-left.view-mode #reportPromptSection,
.report-builder .panel-left.view-mode #reportPromptContent,
.report-builder .panel-left.view-mode #reportVariablesSection,
.report-builder .panel-left.view-mode .report-variable-item,
.report-builder .panel-left.view-mode #reportVariablesContent,
.report-builder .panel-left.view-mode #aiExpandSections,
.report-builder .panel-left.view-mode #reportDatasourcesSection,
.report-builder .panel-left.view-mode #reportDatasourcesContent,
.report-builder .panel-left.edit-mode #viewModeMessage,
.report-builder #viewModeMessage {
  display: none;
}

.report-builder .panel-left.view-mode #viewModeMessage {
  display: block;
}

/* --------------------------------------------------------------------------
   8.14 Intelligence
   --------------------------------------------------------------------------
   `intelligence/graph`, `trenddetail`, `ads` and `addetails`. The two PDF
   renderers keep their inline CSS — a PDF engine has no external stylesheet.

   `.tab-btn` is the class three unrelated surfaces use, and `project.blade.php`
   styles it entirely from `.page-tabs-link` in section 8.5. So the two
   intelligence tab strips stay behind their page scope rather than becoming a
   platform family that would silently restyle the project page.
   -------------------------------------------------------------------------- */
.intel-graph .tab-btn,
.trend-detail .tab-btn {
  background: none;
  border-top: none;
  border-right: none;
  border-left: none;
  border-bottom: 2px solid transparent;
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  transition: all 0.2s;
}

.trend-detail .tab-btn {
  padding: 1rem 0.5rem;
  color: var(--as-gray-600);
}

.intel-graph .tab-btn:not(.active):hover,
.trend-detail .tab-btn:not(.active):hover {
  color: var(--as-gray-700);
}

.trend-detail .tab-btn:not(.active):hover {
  border-bottom-color: var(--as-gray-300);
}

.intel-graph .tab-btn.active,
.trend-detail .tab-btn.active {
  color: var(--as-primary);
  border-bottom-color: var(--as-primary);
}

.intel-graph .view-controls.hidden {
  display: none;
}

.trend-detail .tab-pane {
  animation: intel-tab-fade-in 0.3s;
}

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

/* Sensitive imagery is blurred until hovered. The list and the detail page
   blur by different amounts, which is kept rather than averaged. */
.ads-list .blurred-image,
.ad-detail .blurred-image {
  filter: grayscale(75%) blur(6px);
  transition: filter 0.3s ease;
}

.ad-detail .blurred-image {
  filter: grayscale(75%) blur(8px);
}

.ads-list .blurred-image:hover,
.ad-detail .blurred-image:hover {
  filter: grayscale(0%) blur(0);
}

/* The filter rail scrolls horizontally without showing a bar. */
.ads-list .scrollable-filters {
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.ads-list .scrollable-filters::-webkit-scrollbar {
  display: none;
}

/* --------------------------------------------------------------------------
   8.15 The workflow visualizer
   --------------------------------------------------------------------------
   `workflowvisualizer.blade.php`, entirely behind `.workflow-visualizer`.

   The scope is not optional. The page declares `.btn`, `.btn-primary`,
   `.btn-secondary`, `.btn-danger`, `.btn-success`, `.form-group`,
   `.modal-overlay` and `.flow-node` — the first five are the platform button
   family and the last is the agent-flow canvas's node. Unscoped in a shared
   stylesheet, this block would repaint every button in the app.
   -------------------------------------------------------------------------- */
:root {
  /* The visualizer's own palette. Like the flow canvas above, a diagram
     surface whose ACCENTS are not theme roles: re-reading them from
     `--as-primary` would make a connector look like a link.

     The canvas itself is the exception, and it is the same exception the flow
     canvas already makes. A near-white board with pale grey dots is not an
     accent, it is the page under the diagram, and it stayed that colour on
     both dark themes — the first capture this page ever had reported it as a
     light surface. So these two read the tokens `#flow-canvas-wrapper` reads,
     in the same bg + radial-gradient shape. */
  --wfv-canvas-bg: var(--as-bg);
  --wfv-grid-dot: var(--as-gray-200);
  --wfv-arrow: #6366f1;
  --wfv-step-from: #3b82f6;
  --wfv-step-to: #2563eb;
  --wfv-step-border: #2563eb;
  --wfv-node-radius: 10px;
  --wfv-node-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  --wfv-node-shadow-hover: 0 6px 16px rgba(0, 0, 0, 0.1);
  --wfv-btn-primary: #3b82f6;
  --wfv-btn-success: #10b981;
  --wfv-btn-danger: #ef4444;
  --wfv-btn-secondary: #6b7280;
  --wfv-tooltip-accent: rgba(255, 255, 255, 0.92);
  --wfv-port-default: #6b7280;
  --wfv-port-hover: #3b82f6;
}

.workflow-visualizer .custom-card {
  background-color: var(--as-bg-elevated);
  border-radius: 8px;
  box-shadow: var(--as-shadow-hairline);
}

.workflow-visualizer #flowchart-container {
  position: relative;
  min-height: 600px;
  overflow: hidden;
  border-radius: 0 0 8px 8px;
  background-color: var(--wfv-canvas-bg);
  background-image: radial-gradient(circle, var(--wfv-grid-dot) 1px, transparent 1px);
  background-size: 20px 20px;
}

.workflow-visualizer .flow-node {
  position: absolute;
  z-index: 10;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 10px 12px;
  background: var(--as-bg-elevated);
  border: 2px solid var(--as-gray-200);
  border-radius: var(--wfv-node-radius);
  box-shadow: var(--wfv-node-shadow);
  font-size: 12px;
  text-align: center;
  cursor: pointer;
  user-select: none;
  transition: all 0.2s ease;
}

.workflow-visualizer .flow-node:hover {
  box-shadow: var(--wfv-node-shadow-hover);
  transform: translateY(-1px);
}

.workflow-visualizer .flow-node.status-node {
  z-index: 10;
  width: 140px;
  height: 70px;
}

.workflow-visualizer .flow-node.action-node {
  width: 160px;
  min-height: 70px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.workflow-visualizer .flow-node.action-node:hover {
  box-shadow: var(--wfv-node-shadow-hover);
  transform: translateY(-2px);
}

.workflow-visualizer .step-container {
  position: absolute;
  z-index: 5;
  padding: 50px 20px 20px;
  background: color-mix(in srgb, var(--as-gray-100) 85%, transparent);
  border: 2px solid var(--as-gray-300);
  border-radius: 12px;
  cursor: move;
  user-select: none;
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

.workflow-visualizer .step-container-title {
  position: absolute;
  top: 12px;
  left: 12px;
  color: var(--as-gray-700);
  font-size: 14px;
  font-weight: 600;
  pointer-events: none;
}

.workflow-visualizer .action-tooltip {
  position: absolute;
  z-index: 1000;
  max-width: 300px;
  padding: 12px;
  background: var(--wfv-tooltip-accent);
  border: 1px solid var(--as-gray-200);
  border-radius: 12px;
  box-shadow: var(--as-shadow-raised);
  font-size: 12px;
  pointer-events: none;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.workflow-visualizer .action-tooltip-title {
  margin-bottom: 8px;
  color: var(--as-gray-900);
  font-size: 13px;
  font-weight: 600;
}

.workflow-visualizer .action-tooltip-row {
  display: flex;
  gap: 8px;
  margin-bottom: 4px;
}

.workflow-visualizer .action-tooltip-label {
  min-width: 80px;
  color: var(--as-gray-600);
}

.workflow-visualizer .action-tooltip-value {
  color: var(--as-gray-900);
  font-weight: 500;
}

.workflow-visualizer .btn-icon-square {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
}

.workflow-visualizer .btn-icon-square i {
  font-size: 18px;
}

.workflow-visualizer .flow-node-title {
  margin-bottom: 2px;
  color: var(--as-gray-900);
  font-size: 11px;
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
}

.workflow-visualizer .flow-node-description {
  color: var(--as-gray-600);
  font-size: 10px;
  line-height: 1.2;
  text-align: center;
}

/* A second `.connection-point` rule in the view carried `!important` on every
   declaration, to stop the form-input family resizing the ports. Merged here,
   and the `!important`s dropped: with the whole page behind
   `.workflow-visualizer` the ports are two classes deep and the form rules
   name an `input`, so there is nothing left for them to beat. */
.workflow-visualizer .connection-point {
  position: absolute;
  z-index: 20;
  width: 12px;
  height: 12px;
  margin: 0;
  padding: 0;
  border: 2px solid var(--wfv-port-default);
  border-radius: 50%;
  background: var(--as-bg-elevated);
  cursor: pointer;
  transition: all 0.2s ease;
}

.workflow-visualizer .connection-point.conn-output {
  top: 50%;
  right: -6px;
  transform: translateY(-50%);
}

.workflow-visualizer .connection-point.conn-input {
  top: 50%;
  left: -6px;
  transform: translateY(-50%);
}

.workflow-visualizer .connection-point:hover {
  border-color: var(--wfv-port-hover);
  background: var(--wfv-port-hover);
  transform: translateY(-50%) scale(1.2);
}

.workflow-visualizer .connection-line {
  fill: none;
  stroke-width: 2;
  cursor: pointer;
  transition: all 0.2s ease;
}

.workflow-visualizer .connection-line:hover {
  stroke: var(--wfv-port-hover);
  stroke-width: 3;
}

.workflow-visualizer .connection-line.action-arrow {
  marker-end: url("#arrowhead");
}

.workflow-visualizer .modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  background-color: var(--as-coal-clarity);
}

.workflow-visualizer .modal-overlay.active {
  display: flex;
}

.workflow-visualizer .form-group {
  margin-bottom: 16px;
}

.workflow-visualizer .form-group label {
  display: block;
  margin-bottom: 6px;
  color: var(--as-gray-700);
  font-weight: 600;
}

.workflow-visualizer .form-group input[type="text"],
.workflow-visualizer .form-group input[type="number"],
.workflow-visualizer .form-group select,
.workflow-visualizer .form-group textarea {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--as-gray-300);
  border-radius: 6px;
  font-size: 14px;
}

.workflow-visualizer .form-group input[type="checkbox"] {
  margin-right: 8px;
}

.workflow-visualizer .form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

.workflow-visualizer .btn {
  padding: 10px 20px;
  border: none;
  border-radius: 6px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.workflow-visualizer .btn-primary {
  background-color: var(--wfv-btn-primary);
  color: #fff;
}

.workflow-visualizer .btn-primary:hover {
  background-color: var(--wfv-step-to);
}

.workflow-visualizer .btn-secondary {
  background-color: var(--wfv-btn-secondary);
  color: #fff;
}

.workflow-visualizer .btn-secondary:hover {
  background-color: var(--as-gray-700);
}

.workflow-visualizer .btn-danger {
  background-color: var(--wfv-btn-danger);
  color: #fff;
}

.workflow-visualizer .btn-danger:hover {
  background-color: var(--as-danger);
}

.workflow-visualizer .btn-success {
  background-color: var(--wfv-btn-success);
  color: #fff;
}

.workflow-visualizer .btn-success:hover {
  background-color: var(--as-success-hover);
}

.workflow-visualizer .conditional-field {
  display: none;
}

.workflow-visualizer .conditional-field.visible {
  display: block;
}

.workflow-visualizer .tooltip-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin-left: 4px;
  background: var(--wfv-btn-primary);
  border-radius: 50%;
  color: #fff;
  font-size: 12px;
  font-weight: bold;
  line-height: 16px;
  text-align: center;
  vertical-align: middle;
  cursor: help;
}

.workflow-visualizer .tooltip-icon:hover {
  background: var(--wfv-step-to);
}

/* --------------------------------------------------------------------------
   8.16 Billing
   --------------------------------------------------------------------------
   `subscriptions/checkout`, `payment-methods` and `plans`. The two tab strips
   were one rule set written twice, once per view.
   -------------------------------------------------------------------------- */
.payment-tab {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.75rem;
  padding: 1rem 1.25rem;
  background-color: var(--as-bg-elevated);
  border: 2px solid var(--as-gray-200);
  border-radius: var(--as-radius);
  cursor: pointer;
  transition: all 0.2s ease;
}

.payment-tab.active {
  border-color: var(--as-primary);
  box-shadow: 0 0 0 1px var(--as-primary);
}

.payment-tab:not(.active):hover {
  background-color: var(--as-gray-100);
  border-color: var(--as-gray-300);
}

.payment-tab-icon {
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  margin: 0 0.5rem;
  stroke: var(--as-gray-500);
}

.pm-tab,
.ach-sub-tab {
  cursor: pointer;
  transition: all 0.2s ease;
}

.pm-tab.active,
.ach-sub-tab.active {
  background-color: var(--as-bg-elevated);
  box-shadow: var(--as-shadow-hairline);
  color: var(--as-info-filled);
  font-weight: 600;
}

.pm-tab:not(.active):hover,
.ach-sub-tab:not(.active):hover {
  background-color: var(--as-gray-100);
}

/* Stripe's own hosted elements. The Link and autofill buttons are hidden
   because the checkout collects the card details itself. */
.StripeElement .Link-button,
.StripeElement [data-testid="linkButton"],
.StripeElement [data-testid="autofill-button"] {
  display: none;
}

#address-line-element .p-FieldLabel {
  padding-bottom: 0.25rem;
  font-size: 1rem;
  font-weight: 700;
}

/* The seat count is a number input whose spinner would let a click change
   the price without the user noticing. */
.seat-input::-webkit-outer-spin-button,
.seat-input::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}

.seat-input[type="number"] {
  appearance: textfield;
}

/* --------------------------------------------------------------------------
   8.17 Account, settings and admin
   --------------------------------------------------------------------------
   The profile avatar control, the two AI settings pages and the AI pricing
   admin. All four were written as literal-coloured cards; the card chrome is
   one shared rule now.
   -------------------------------------------------------------------------- */
.avatar-edit {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.avatar-edit__trigger {
  box-sizing: border-box;
  display: block;
  width: 5rem;
  height: 5rem;
  padding: 0;
  overflow: hidden;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 9999px;
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.avatar-edit__trigger:hover {
  opacity: 0.9;
}

.avatar-edit__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.avatar-edit__spinner {
  position: absolute;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--as-bg-elevated) 70%, transparent);
  border-radius: 9999px;
}

.avatar-edit__spinner .icon {
  font-size: 1.5rem;
}

/* Positioning and hover-reveal only — `.btn.btn-xs.btn-icon.btn-danger` owns
   the look. */
.avatar-edit__remove {
  position: absolute;
  top: -6px;
  right: -6px;
  z-index: 2;
  opacity: 0;
  box-shadow: var(--as-shadow-hairline);
  transition: opacity 0.15s ease;
}

.avatar-edit:hover .avatar-edit__remove,
.avatar-edit:focus-within .avatar-edit__remove {
  opacity: 1;
}

.ai-usage-wrap {
  max-width: 1080px;
  margin: 1.5em auto;
}

.ai-pricing-wrap {
  max-width: 1200px;
  margin: 1.5em auto;
}

.ai-usage-card,
.ai-pricing-card,
.ai-privacy-card {
  margin-bottom: 1.2em;
  padding: 1.2em 1.4em;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: 10px;
}

.ai-privacy-card {
  max-width: 640px;
  margin: 1.5em auto;
  padding: 1.5em;
}

.ai-usage-card h3,
.ai-pricing-card h3 {
  margin-top: 0;
  color: var(--as-gray-900);
  font-size: 1rem;
  font-weight: 600;
}

.ai-privacy-card h2 {
  margin-top: 0;
}

.ai-usage-table,
.ai-pricing-table {
  width: 100%;
  border-collapse: collapse;
}

.ai-usage-table th,
.ai-usage-table td,
.ai-pricing-table th,
.ai-pricing-table td {
  padding: 0.5em 0.7em;
  border-bottom: 1px solid var(--as-gray-200);
  font-size: 0.88rem;
  text-align: left;
}

.ai-usage-table th,
.ai-pricing-table th {
  color: var(--as-gray-600);
  font-weight: 500;
}

.ai-usage-meter {
  height: 14px;
  margin: 0.5em 0 0.7em;
  overflow: hidden;
  background: var(--as-gray-200);
  border-radius: 999px;
}

/* The meter's three states are a gradient rather than a flat fill so the
   band reads as a scale; the stops are the semantic tokens. */
.ai-usage-meter-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--as-primary), var(--as-info));
  transition: width 0.4s ease;
}

.ai-usage-meter-fill.warn {
  background: linear-gradient(90deg, var(--as-warning), var(--as-danger));
}

.ai-usage-meter-fill.over {
  background: linear-gradient(90deg, var(--as-danger), var(--as-danger-hover));
}

.ai-usage-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5em;
}

.ai-usage-kpi {
  flex: 1 1 200px;
}

.ai-usage-kpi .kpi-num {
  color: var(--as-gray-900);
  font-size: 1.6rem;
  font-weight: 600;
}

.ai-usage-kpi .kpi-label {
  color: var(--as-gray-600);
  font-size: 0.85rem;
}

.ai-usage-trend {
  height: 260px;
}

.ai-usage-banner {
  display: none;
  margin-bottom: 1em;
  padding: 0.7em 1em;
  border-radius: 8px;
  font-size: 0.9rem;
}

.ai-usage-banner.warn {
  background: var(--as-warning-light);
  border: 1px solid var(--as-warning-muted);
  color: var(--as-warning-hover);
}

.ai-usage-banner.over,
.ai-privacy-strict {
  background: var(--as-danger-light);
  border: 1px solid var(--as-danger-muted);
  color: var(--as-danger-hover);
}

.ai-privacy-strict {
  margin-bottom: 1em;
  padding: 0.6em 0.8em;
  background: var(--as-warning-light);
  border-color: var(--as-warning-muted);
  border-radius: 6px;
  color: var(--as-warning-hover);
  font-size: 0.85rem;
}

.ai-usage-empty {
  padding: 1.2em;
  color: var(--as-gray-500);
  text-align: center;
}

.ai-usage-actions,
.ai-pricing-actions {
  display: flex;
  gap: 0.8em;
  justify-content: flex-end;
  margin-top: 1em;
}

.ai-pricing-actions {
  gap: 0.6em;
  margin-top: 0;
}

.ai-pricing-row-actions .btn {
  margin-right: 0.4em;
}

.ai-pricing-form {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.7em 1em;
  margin-top: 1em;
}

.ai-pricing-form label {
  display: block;
  margin-bottom: 0.2em;
  color: var(--as-gray-600);
  font-size: 0.78rem;
}

.ai-pricing-form input,
.ai-pricing-form select {
  width: 100%;
}

.ai-pricing-simulate {
  padding: 0.8em 1em;
  background: var(--as-gray-100);
  border-radius: 8px;
}

.ai-pricing-margin-good { color: var(--as-success); }
.ai-pricing-margin-bad { color: var(--as-danger-hover); }

.ai-pricing-source,
.ai-privacy-source {
  color: var(--as-gray-500);
  font-size: 0.72rem;
}

.ai-privacy-source {
  margin-left: 0.4em;
}

.ai-privacy-row {
  margin-bottom: 1.2em;
}

.ai-privacy-row label {
  display: block;
  margin-bottom: 0.3em;
  font-weight: 500;
}

.ai-privacy-row .hint {
  margin-top: 0.2em;
  color: var(--as-gray-600);
  font-size: 0.85rem;
}

/* --------------------------------------------------------------------------
   8.18 Compliance documents
   --------------------------------------------------------------------------
   The Trust & Compliance Center under `/support`, whose layout is
   `resources/views/support/layout.blade.php`. The pages are static evidence
   documents, deliberately outside the app chrome, and they read as paper: one
   column, generous measure, and a table that looks like a table.

   `resources/views-export/support/layout.blade.php` shadows this layout for
   the DOCX export and keeps its own inline copy on purpose — LibreOffice
   converts a single self-contained file, with no stylesheet to fetch.
   -------------------------------------------------------------------------- */
body.doc-body {
  margin: 0;
  background: var(--as-bg);
  color: var(--as-gray-800);
  font-family: var(--as-font-body);
}

/* Offset for the site's fixed header (`partials.header`). */
.doc-shell {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding-top: var(--as-header-height);
}

.doc-main {
  flex: 1;
}

.doc-container {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 24px 64px;
}

.doc-paper {
  padding: 40px 48px;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: var(--as-radius);
  box-shadow: var(--as-shadow-hairline);
}

.doc-paper h1 {
  margin: 0 0 6px;
  color: var(--as-gray-900);
  font-size: 1.75rem;
  font-weight: 600;
  line-height: 1.2;
}

.doc-eyebrow {
  margin-bottom: 8px;
  color: var(--as-primary);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.doc-lead {
  margin: 0 0 24px;
  color: var(--as-gray-600);
  font-size: 1rem;
}

.doc-paper h2 {
  margin: 34px 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--as-gray-200);
  color: var(--as-gray-900);
  font-size: 1.25rem;
  font-weight: 600;
}

.doc-paper h3 {
  margin: 22px 0 8px;
  color: var(--as-gray-800);
  font-size: 1.0625rem;
  font-weight: 600;
}

.doc-paper p,
.doc-paper li {
  color: var(--as-gray-700);
  font-size: 0.9375rem;
  line-height: 1.65;
}

.doc-paper ul,
.doc-paper ol {
  margin: 8px 0 14px;
  padding-left: 22px;
}

.doc-paper li {
  margin-bottom: 5px;
}

.doc-paper a {
  color: var(--as-primary);
}

.doc-table {
  width: 100%;
  margin: 12px 0 20px;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.doc-table th,
.doc-table td {
  padding: 9px 12px;
  border: 1px solid var(--as-gray-200);
  text-align: left;
  vertical-align: top;
}

.doc-table th {
  background: var(--as-gray-100);
  color: var(--as-gray-800);
  font-weight: 600;
}

.doc-table tbody tr:nth-child(even) {
  background: var(--as-surface-flat);
}

.doc-control {
  margin: 0 0 28px;
  padding: 4px 18px;
  background: var(--as-gray-100);
  border: 1px solid var(--as-gray-200);
  border-radius: 10px;
}

.doc-control table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
}

.doc-control td {
  padding: 8px 10px;
  border-bottom: 1px solid var(--as-gray-200);
}

.doc-control tr:last-child td {
  border-bottom: none;
}

.doc-control td.k {
  width: 160px;
  color: var(--as-gray-600);
  font-weight: 500;
  white-space: nowrap;
}

.doc-control td.v {
  color: var(--as-gray-800);
  font-weight: 500;
}

.doc-callout {
  margin: 18px 0;
  padding: 14px 18px;
  border: 1px solid transparent;
  border-radius: 10px;
  font-size: 0.875rem;
}

/* First child only — unscoped this blocked mid-sentence emphasis too. */
.doc-callout > strong:first-child {
  display: block;
  margin-bottom: 4px;
}

.doc-callout--info {
  background: var(--as-primary-light);
  border-color: var(--as-primary-muted);
  color: var(--as-gray-800);
}

.doc-callout--note {
  background: var(--as-gray-100);
  border-color: var(--as-gray-200);
  color: var(--as-gray-700);
}

.doc-callout--warn {
  background: var(--as-warning-light);
  border-color: var(--as-warning-muted);
  color: var(--as-warning-hover);
}

/* The documents' severity and workflow-state pills are the `.pill` family in
   section 6 — the same six tones, reached by the same modifiers. They used to
   be nine literal tints scoped to `.doc-body`, which is the shape the family
   was extracted FROM; severity is the one category scale where the semantic
   mapping is the right one (a critical finding IS the danger tone), so unlike
   `.ctype-pill` there was nothing to preserve by keeping them apart. */

/* The mock browser frame the "screenshot evidence" pages use. It is a picture
   OF a screen, so its chrome and its console stay fixed-dark on every theme —
   a terminal that turns white with the page is not evidence of anything. */
.mock-frame {
  margin: 18px 0;
  overflow: hidden;
  border: 1px solid var(--as-gray-300);
  border-radius: 10px;
  box-shadow: var(--as-shadow-raised);
}

.mock-chrome {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: #1f2937;
}

.mock-dot {
  display: inline-block;
  width: 11px;
  height: 11px;
  border-radius: 50%;
}

.mock-url {
  flex: 1;
  margin-left: 8px;
  padding: 4px 12px;
  background: #374151;
  border-radius: 6px;
  color: #9ca3af;
  font-family: monospace;
  font-size: 0.75rem;
}

/* `white-space: pre` pairs with the horizontal scroll — without it the
   console collapses to one line. */
.mock-terminal {
  padding: 18px 20px;
  overflow-x: auto;
  background: #0b1020;
  color: #cdd6e4;
  font-family: ui-monospace, sfmono-regular, menlo, monospace;
  font-size: 0.8125rem;
  line-height: 1.6;
  white-space: pre;
}

.mock-terminal .c-green { color: #4ade80; }
.mock-terminal .c-cyan { color: #67e8f9; }
.mock-terminal .c-amber { color: #fbbf24; }
.mock-terminal .c-red { color: #f87171; }
.mock-terminal .c-gray { color: #6b7280; }
.mock-terminal .c-white { color: #f3f4f6; }

.doc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
  margin: 18px 0;
}

.doc-card {
  display: flex;
  flex-direction: column;
  padding: 20px;
  background: var(--as-bg-elevated);
  border: 1px solid var(--as-gray-200);
  border-radius: var(--as-radius);
  text-decoration: none;
  transition: box-shadow 0.15s, transform 0.15s, border-color 0.15s;
}

a.doc-card:hover {
  border-color: var(--as-primary);
  box-shadow: var(--as-shadow-soft);
  transform: translateY(-2px);
}

.doc-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin-bottom: 12px;
  background: var(--as-primary-light);
  border-radius: 10px;
  color: var(--as-primary);
}

.doc-card-icon svg {
  width: 22px;
  height: 22px;
}

.doc-card h3 {
  margin: 0 0 6px;
  color: var(--as-gray-900);
  font-size: 1rem;
  font-weight: 600;
}

.doc-card p {
  margin: 0;
  color: var(--as-gray-500);
  font-size: 0.8125rem;
  line-height: 1.55;
}

.doc-section-label {
  margin: 32px 0 4px;
  color: var(--as-gray-500);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.doc-footer {
  background: var(--as-bg-elevated);
  border-top: 1px solid var(--as-gray-200);
}

.doc-footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  max-width: 1080px;
  margin: 0 auto;
  padding: 18px 24px;
  color: var(--as-gray-500);
  font-size: 0.8125rem;
}

.doc-footer a {
  color: var(--as-gray-500);
  text-decoration: none;
}

.doc-footer a:hover {
  color: var(--as-primary);
}

@media print {
  /* The site bar and the footer are chrome, not document. */
  body.doc-body .doc-footer,
  body.doc-body #header,
  body.doc-body .header {
    display: none;
  }

  body.doc-body .doc-shell {
    padding-top: 0;
  }

  body.doc-body {
    background: #fff;
  }

  body.doc-body .doc-paper {
    padding: 0;
    border: none;
    box-shadow: none;
  }
}

@media (max-width: 640px) {
  .doc-paper {
    padding: 24px 20px;
  }

  .doc-container {
    padding: 18px 12px 48px;
  }
}

/* --------------------------------------------------------------------------
   8.19 Dialog surfaces
   --------------------------------------------------------------------------
   Rules that belong to one dialog rather than to the modal family in section
   6. Every one is scoped by the dialog's own id: between them these blocks
   redeclared `.select`, `.input`, `.border-red-500`, `.animate-spin`,
   `#drop-area`, `.filename` and `.delete-btn`, which are respectively two
   platform families, two generated utilities and three names two different
   dialogs each defined differently.
   -------------------------------------------------------------------------- */

/* --- The query builder ---------------------------------------------------- */
#queryBuilderModal .condition-operator-btn.active {
  background-color: var(--as-primary-light);
  color: var(--as-primary);
}

#queryBuilderModal .condition-row {
  min-height: 40px;
}

#queryBuilderModal .select,
#queryBuilderModal .input {
  min-height: 38px;
}

#queryBuilderModal #sqlPreview {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

#queryBuilderModal .value-container input:disabled {
  background-color: var(--as-gray-100);
  opacity: 0.6;
}

/* Validation borders. These were `.border-red-500` / `.border-green-500` with
   `!important` — an override of two generated utilities, which unscoped would
   have repainted every border in the app that uses them. */
#queryBuilderModal .border-red-500 {
  border-color: var(--as-danger);
}

#queryBuilderModal .border-green-500 {
  border-color: var(--as-success);
}

#queryBuilderModal .validation-message {
  margin-top: 0.25rem;
  color: var(--as-danger);
  font-size: 0.75rem;
}

#queryBuilderModal .value-container.flex .value-input,
#queryBuilderModal .value-container.flex .value-input-2 {
  flex: 1;
  min-width: 0;
}

#queryBuilderModal .between-and-label {
  flex-shrink: 0;
  user-select: none;
}

/* The AND/OR switch. It is a labelled two-state control rather than a
   checkbox, so the track carries the two words and the knob covers one. */
#queryBuilderModal .toggle-switch {
  position: relative;
  display: inline-block;
  width: 120px;
  height: 40px;
  cursor: pointer;
}

#queryBuilderModal .toggle-switch input {
  width: 0;
  height: 0;
  opacity: 0;
}

#queryBuilderModal .toggle-slider {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 8px;
  background-color: var(--as-primary);
  border-radius: 20px;
  box-shadow: var(--as-shadow-hairline);
  transition: 0.3s;
}

#queryBuilderModal .toggle-slider::before {
  content: "";
  position: absolute;
  bottom: 4px;
  left: 4px;
  width: 56px;
  height: 32px;
  background-color: var(--as-bg-elevated);
  border-radius: 16px;
  box-shadow: var(--as-shadow-hairline);
  transition: 0.3s;
}

#queryBuilderModal .toggle-switch input:checked + .toggle-slider {
  background-color: var(--as-brand);
}

#queryBuilderModal .toggle-switch input:checked + .toggle-slider::before {
  transform: translateX(56px);
}

#queryBuilderModal .toggle-text {
  position: relative;
  z-index: 1;
  color: var(--as-primary-inverse);
  font-size: 0.75rem;
  font-weight: 600;
  pointer-events: none;
  transition: opacity 0.3s;
}

#queryBuilderModal .toggle-text-left {
  opacity: 1;
}

#queryBuilderModal .toggle-text-right {
  opacity: 0.7;
}

#queryBuilderModal .toggle-switch input:checked + .toggle-slider .toggle-text-left {
  opacity: 0.7;
}

#queryBuilderModal .toggle-switch input:checked + .toggle-slider .toggle-text-right {
  opacity: 1;
}

#queryBuilderModal .toggle-switch-small {
  width: 80px;
  height: 28px;
}

#queryBuilderModal .toggle-slider-small {
  padding: 0 6px;
  border-radius: 14px;
}

#queryBuilderModal .toggle-slider-small::before {
  bottom: 3px;
  left: 3px;
  width: 36px;
  height: 22px;
  border-radius: 11px;
}

#queryBuilderModal .toggle-switch-small input:checked + .toggle-slider-small::before {
  transform: translateX(38px);
}

#queryBuilderModal .toggle-text-small {
  font-size: 0.625rem;
  font-weight: 500;
}

/* The rule between two condition groups, with the AND/OR control sitting on
   top of it. */
#queryBuilderModal .rule-group-separator {
  position: relative;
}

#queryBuilderModal .rule-group-separator::before {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  left: 0;
  z-index: 0;
  height: 1px;
  background: linear-gradient(
    to right,
    transparent,
    var(--as-gray-200) 20%,
    var(--as-gray-200) 80%,
    transparent
  );
}

#queryBuilderModal .rule-group-operator-container {
  position: relative;
  z-index: 1;
  padding: 0 16px;
  background: var(--as-bg-elevated);
}

/* --- The rule builder ----------------------------------------------------- */
#actionBuilderModal .action-group {
  animation: rule-action-slide-in 0.3s ease-out;
}

#actionBuilderModal .action-row {
  transition: all 0.2s;
}

#actionBuilderModal .action-row:hover {
  border-color: var(--as-success);
}

@keyframes rule-action-slide-in {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* --- The AI rule recommendations dialog ----------------------------------- */
#aiRecommendRulesModal #ai-recommendations-list .border-gray-200:hover {
  transform: translateY(-2px);
}

#aiRecommendRulesModal #ai-status-message {
  animation: ai-status-fade-in 0.5s ease-in-out;
}

#aiRecommendRulesModal #ai-progress-bar {
  transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}

/* `spin` is the generated utilities layer's; the second rhythm is ours, and
   is named so the two cannot collide. */
#aiRecommendRulesModal #ai-loading-state svg.animate-spin {
  animation: spin 1s linear infinite, ai-thinking-pulse 2s ease-in-out infinite;
}

@keyframes ai-status-fade-in {
  from {
    opacity: 0;
    transform: translateY(-5px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes ai-thinking-pulse {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }

  50% {
    opacity: 0.8;
    transform: scale(1.05);
  }
}

/* --- The bulk upload dialog ------------------------------------------------ */
#bulkuploadbox #drop-area {
  width: 100%;
  min-height: 200px;
  margin: 0 auto;
  border: 2px dashed var(--as-gray-300);
  border-radius: var(--as-radius);
  line-height: 200px;
  text-align: center;
  cursor: pointer;
}

#bulkuploadbox #drop-area.drag-over {
  background-color: var(--as-gray-100);
}

#bulkuploadbox #preview-container {
  text-align: center;
}

#bulkuploadbox .preview-image {
  width: 100px;
  height: 100px;
  margin: 10px auto 5px;
  object-fit: cover;
}

#bulkuploadbox .file-preview-container {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: auto;
  margin: 0;
}

#bulkuploadbox .filename {
  position: relative;
  width: 100%;
  font-size: 12px;
  white-space: normal;
  overflow-wrap: anywhere;
}

#bulkuploadbox .delete-btn {
  position: absolute;
  top: 0;
  right: 0;
  width: 20px;
  height: 20px;
  background: color-mix(in srgb, var(--as-danger) 70%, transparent);
  border: none;
  border-radius: 50%;
  color: var(--as-danger-inverse);
  line-height: 18px;
  text-align: center;
  cursor: pointer;
}

#bulkuploadbox .delete-btn:hover {
  background: var(--as-danger);
}

#bulkuploadbox .shake {
  animation: as-shake 0.3s;
}

@keyframes as-shake {
  0%,
  100% { transform: translateX(0); }
  25%,
  75% { transform: translateX(-5px); }
  50% { transform: translateX(5px); }
}

/* --- The import dialog ----------------------------------------------------- */
#import-modal #drop-area {
  width: 100%;
  min-height: 200px;
  margin: 0 auto;
  border: 2px dashed var(--as-gray-300);
  border-radius: var(--as-radius);
  line-height: 200px;
  text-align: center;
  cursor: pointer;
}

#import-modal #drop-area.drag-over {
  background-color: var(--as-gray-100);
}

#import-modal #preview-container {
  text-align: center;
}

/* The import preview is a row per file — thumbnail, name, size — rather than
   the bulk uploader's grid of tiles, which is why the two dialogs size the
   same class names differently. */
#import-modal .preview-image {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  margin: 0 12px 0 0;
  object-fit: cover;
}

#import-modal .file-preview-container {
  position: relative;
  display: flex;
  flex-direction: row;
  width: 100%;
  min-height: 60px;
  margin: 0 0 8px;
  padding: 8px 12px;
  background: var(--as-primary-light);
  border-radius: 0.5rem;
  text-align: left;
}

#import-modal .filename {
  position: relative;
  width: 100%;
  margin-bottom: 2px;
  color: var(--as-primary);
  font-size: 13px;
  font-weight: 500;
  white-space: normal;
  overflow-wrap: anywhere;
}

#import-modal .delete-btn {
  position: absolute;
  top: 0;
  right: 0;
  width: 24px;
  height: 24px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 50%;
  color: var(--as-gray-600);
  line-height: 24px;
  text-align: center;
  cursor: pointer;
  transition: background 0.15s;
}

#import-modal .delete-btn:hover {
  background: var(--as-gray-100);
  color: var(--as-primary);
}

#import-modal .dots {
  display: inline-block;
  animation: import-dot-pulse 1.5s infinite ease-in-out;
}

#import-modal #progress-message {
  display: flex;
  flex-direction: column;
  min-height: 2em;
  margin-top: 1em;
}

/* A PDF with no tabular data cannot be imported, so the preview renders a
   marker and the dialog's Import button goes away. This was a `<style>` block
   inside the conditionally-rendered preview partial. */
#import-modal:has(.import-no-tabular-data) #import-import-btn-header {
  display: none;
}

@keyframes import-dot-pulse {
  0%,
  100% { opacity: 0.4; }
  50% { opacity: 1; }
}

/* --- The search dialog ----------------------------------------------------- */
#search_results .card {
  width: auto;
  max-width: 200px;
}

#search_results .card:hover {
  background-color: var(--as-gray-100);
}

#search_results > .menu-item > .menu-link {
  margin: 0;
  padding: 0.75rem 1.25rem;
  border-radius: 0;
}

#search_results .card .menu-link {
  margin: 0.5rem;
  padding: 0.25rem;
}

#search_results .card:hover,
#search_results .menu-link:hover {
  color: var(--as-text);
}

/* ==========================================================================
   9. Responsive
   ==========================================================================
   The design reference's rule 11: layouts, components, modals, drawers,
   toolbars, forms, tables and editor chrome all adapt to viewport width AND
   height, to orientation, and to a device's safe areas — nothing is
   desktop-only unless that is stated.

   The rules here are the ones no single page can own: the shell's shrink
   floor, the overlays' bounds, the coarse-pointer target floor. Page-level
   adaptation (a grid that goes from five columns to two, a toolbar that wraps)
   lives in the markup as responsive utilities, because that is where a reader
   looks for it — and the chrome's own safe-area insets live with the chrome,
   in section 5, so `.header-appslate` and `.sidebar-appslate` each stay one
   rule rather than two in different sections. */

/* --- The shell's shrink floor --------------------------------------------
   The rule behind most of the horizontal overflow this app had on a phone.

   `.app-shell` (the sidebar + content row) and `.wrapper` (the header +
   content column) are flex items, and a flex item's `min-width: auto` floors
   it at the MIN-CONTENT WIDTH OF EVERYTHING INSIDE IT. One wide table, eight
   levels down and already sitting in its own `.scrollable-x-auto`, therefore
   pushed the whole shell wider than the viewport — the scroll container never
   got the chance to scroll, because its parent had grown to fit it. On its own
   this cleared two of the seven pages that overflowed at 360 and shrank three
   more; the rest each had one page-level cause as well.

   Both need it: `min-width` does not lower a box's min-content contribution to
   its own parent, so flooring only the inner one leaves the outer one wide.
   Verified by measurement — either alone leaves all six pages unchanged.

   At any width where the content already fits this changes nothing, which is
   why it is unconditional rather than inside a media query. */
.app-shell,
.wrapper {
  min-width: 0;
}

/* --- Grid tracks that cannot shrink ---------------------------------------
   `.panel-container` (the shared two-pane shell behind the pattern builder,
   object details and the agent workspaces) declares its tracks as bare `1fr`,
   which is `minmax(auto, 1fr)` — and that `auto` floor is the largest
   min-content contribution of the items in the track. A tab strip whose labels
   do not wrap therefore widened the whole page rather than scrolling.

   Flooring the ITEMS, not redeclaring the tracks: an item's minimum
   contribution respects its own `min-width`, so this reaches every variant
   (`--compact`, `--zero`, `.grid-12`, `.hide-sidebar`) and every breakpoint
   without copying the track values out of the frozen stylesheet, where they
   would then quietly drift. */
.panel-container > * {
  min-width: 0;
}

/* --- The Gantt's split pane ------------------------------------------------
   The list pane is 45% of the row but never below 300px, which on a 360px
   screen left the chart 44px — the view's whole point, reduced to a sliver.
   Below the tablet breakpoint that floor comes off and the split stays
   proportional: the two panes are aligned row for row (43px rows under a 60px
   header), so stacking them would break the correspondence they exist for.
   Only the floor is answered — the base rule's 500px ceiling cannot bind under
   768px anyway, since 45% of it is 345px. */
@media (max-width: 767px) {
  #list-gantt-list-pane {
    min-width: 8rem;
  }
}

/* --- Overlays: the viewport they are pinned to ----------------------------
   `position: fixed` resolves against the LARGE viewport — the page as it
   measures with the mobile browser's URL bar retracted. While that bar is
   showing, the bottom of a full-screen overlay sits below the glass, so the
   last rows of a scrolled dialog and anything docked to its bottom edge could
   not be reached at all. `dvh` is the viewport as it currently is.

   The padding is the frozen stylesheet's own 1.25rem, restated so the device's
   inset can raise it. `max()` is what keeps it: a device reporting no inset —
   every desktop — keeps the design's value instead of losing it. The insets
   are only non-zero because the layouts declare `viewport-fit=cover`; without
   that a notch simply letterboxes the page and every `env()` here is 0. */
.modal {
  height: 100dvh;
  padding-top: max(1.25rem, env(safe-area-inset-top));
  padding-right: max(1.25rem, env(safe-area-inset-right));
  padding-bottom: max(1.25rem, env(safe-area-inset-bottom));
  padding-left: max(1.25rem, env(safe-area-inset-left));
}

/* --- Touch targets --------------------------------------------------------
   An icon button sitting inside a field wrapper releases `.btn-icon`'s 2.5rem
   box so it does not force the field taller — and the password-reveal control
   then collapsed onto its glyph: a 13px target, at every width and for every
   pointer. The box is released; the hit area is not.

   The three release declarations moved here from `styles.css`, which declared
   this exact selector 11,583 lines in. Two files describing one element is how
   a later edit changes the size in one of them and leaves the floor behind in
   the other, and the frozen sheet is the copy nobody reads. */
.input:not(input) .btn-icon {
  border: 0;
  height: auto;
  width: auto;
  min-width: 2rem;
  min-height: 2rem;
}

/* A finger is about 9mm across, so under a coarse pointer the controls that
   are compact by design get a floor. This is deliberately not a width media
   query: a phone and a touch laptop want the same target, and a narrow desktop
   window wants none of it. Only minimums, so nothing here can shrink a control
   that is already comfortable.

   Every member was measured under a real touch context before being listed,
   and every one moves: the checkbox 18→24, `.chip-action` 12×18→24×24 (its
   chip grows with it), the three icon buttons to 44. The tab strips are NOT
   here — `.page-tabs-link` and `.tab-btn` already measure 51px and `.tab`,
   the largest family, 38px, so a floor over them would have been a rule that
   does nothing while implying the other three tab vocabularies were an
   oversight. */
@media (pointer: coarse) {
  input[type='checkbox'],
  input[type='radio'],
  .checkbox,
  .chip-action {
    min-width: 1.5rem;
    min-height: 1.5rem;
  }

  .btn-icon,
  .topbar-action,
  .view-switch > button {
    min-width: 2.75rem;
    min-height: 2.75rem;
  }
}
