/* Quantum Reticulum dashboard — ION design system (docs/ui-ion-design-system.md).
   ION names the ARCHITECTURE — base tokens, derived tints, the skin engine, the
   laws. SLATE is the default SKIN it ships with (2026-09-21): Slate Light on
   :root, Slate Dark on data-theme="dark". The neon look survives as the ION
   preset, stamped from app.js SKINS like the other cosmetic presets.
   ALL color derives from the skin tokens below — components read the legacy
   names (--bg/--panel/--accent/…), which are aliased to skin tokens, so a
   per-user skin only has to override the base tokens.
   Laws: ion = the only interactive color · plasma = AI only · ok/warn/bad are
   entity STATUS and never decorate · severity has its own locked --sev-* scale ·
   liveness is signalled per-skin (glow under ION, motion + fill under Slate) ·
   mono carries the DATA. */
* { box-sizing: border-box; margin: 0; padding: 0; }

/* .skin-probe-light/-dark let JS read these palettes off an offscreen element
   (app.js cssNativeSkinVars, for the Appearance preview chips). Without them a
   probe would only inherit :root's tokens — `:root[data-theme=…]` can never
   match a <div> — and every preview chip would show the ACTIVE skin's colours.
   This keeps SLATE LIGHT + SLATE DARK defined once, here, instead of duplicated
   in JS. The five cosmetic presets (ION, TRON, …) stamp these same token names
   inline from app.js SKINS.
   These are the BASE tokens — the only ones a skin sets. Severity (--sev-*) is
   deliberately NOT one of them: it has its own locked block below the skins, and
   no skin, preset or cached value may set it.
   ⚠ The three lengths (--glow/--blur/--tick) are 0px, never 0: they are
   multiplied inside calc(), where a unitless zero is not a length. */
/* SLATE LIGHT — the default identity (2026-09-21). Light neutral content,
   dark slate sidebar, navy interactive. Ratios: the Slate handoff's token sheet
   §5 and its phase 4 spec §4.5 (the sidebar ramp); re-measure with
   .claude/skills/webui-audit/passes/slate/contrast.py on any base-token change.
   NOTE: the sidebar group is identical in both Slate schemes — the shell is
   the constant, only the content ground flips. */
:root, :root[data-theme="light"], .skin-probe-light {
  --void:        #f6f6f5;
  --panel:       #ffffff;
  --panel2:      #fafaf9;
  --panel-solid: var(--panel);   /* D412 (111): the opaque ground a badge's wash is mixed into - the table's own ground. Slate's
                                    --panel is opaque in both schemes; each cosmetic preset stamps its glass --panel composited
                                    over its --void, as a literal (app.js SKINS) */
  --console:     #101828;   /* terminals stay dark in every skin */
  --console-text: #e8f1ff;
  --console-dim: #cfe0f5;
  --console-faint: #8b929c;   /* both Slate schemes — the console never flips */
  --line:        #e6e6e1;
  --line-strong: #d2d2cb;
  --text:        #1b1c1e;
  --muted:       #5a5d62;
  --faint:       #6c7076;
  --tint1:       transparent;
  --tint2:       transparent;
  --grid:        transparent;
  --ion:         #1e4ba8;
  --plasma:      #5b54b8;
  --ok:          #1a7447;   /* was #1b7a4b — 4.52 on --void, 4.87 on --panel */
  --warn:        #845d00;   /* was #8a6100 — 4.53 on --void, 4.85 on --panel */
  --bad:         #b42318;
  --side:        #171b21;
  --side2:       #232932;
  --side-line:   #2a303a;
  --side-text:   #c5cad2;
  --side-text2:  #8a9099;
  --side-accent: #9abbf4;   /* D410 (111): the shell's accent is constant across a skin's schemes - Slate's dark-scheme --ion (was
                               #7aa5f0); the shell is dark in both, and Slate light's navy --ion read 2.16:1 there */
  --side-crit:   #f0776b;
  --glow:        0px;
  --blur:        0px;
  --tick:        0px;
  --shadow:      rgba(15, 23, 42, 0.10);
  --scrim: rgba(10, 14, 22, 0.62);   /* both Slate schemes — a scrim is always dark */
  color-scheme: light;
}
/* SLATE DARK — same design, same contrast bar, dark content ground. */
:root[data-theme="dark"], .skin-probe-dark {
  --void:        #14171c;
  --panel:       #1b1f26;
  --panel2:      #21262e;
  --console:     #101828;
  --console-text: #e8f1ff;
  --console-dim: #cfe0f5;
  --console-faint: #8b929c;   /* both Slate schemes — the console never flips */
  --line:        #2b3038;
  --line-strong: #3a414c;
  --text:        #e8eaed;
  --muted:       #a4aab3;
  --faint:       #8b929c;
  --tint1:       transparent;
  --tint2:       transparent;
  --grid:        transparent;
  --ion:         #9abbf4;   /* D408 (107): was #7aa5f0 - the primary's weak end read 4.18:1 on --panel; now 4.89 on --panel, 4.51 on --panel2 */
  --plasma:      #9c96e8;
  --ok:          #5cbd85;
  --warn:        #d9b649;
  --bad:         #f0776b;
  --side:        #171b21;   /* identical to light — the shell is constant */
  --side2:       #232932;
  --side-line:   #2a303a;
  --side-text:   #c5cad2;
  --side-text2:  #8a9099;
  --side-accent: #9abbf4;   /* D409, D410, D411 (109, 111): Slate's dark-scheme --ion, as a LITERAL - a custom accent does not
                               reach the shell (D32); driver-d408-accent's DA checks it equals --ion, so it cannot drift silently */
  --side-crit:   #f0776b;
  --glow:        0px;
  --blur:        0px;
  --tick:        0px;
  --shadow:      rgba(0, 0, 0, 0.40);
  --scrim: rgba(10, 14, 22, 0.62);   /* both Slate schemes — a scrim is always dark */
  color-scheme: dark;
}
/* ── SEVERITY — NOT SKINNABLE ─────────────────────────────────────────
   Five tiers, one scale, product-wide: detections, vulnerabilities and
   posture all read these. Locked 2026-09-21. A skin retunes the ground,
   --ion and --plasma; it does not get to redefine what a Critical looks
   like. Both sides measured against their own panel: AA at 12px.
   The bare :root sits on the LIGHT line because light is the default scheme —
   it must move with the default, or an unset data-theme pairs one scheme's
   ground with the other's severity. The five -line hairlines are derived
   below, uniformly, rather than authored per tier. */
:root, :root[data-theme="light"], .skin-probe-light {
  --sev-crit: #b42318;  --sev-crit-bg: #fdf0ee;
  --sev-high: #b45309;  --sev-high-bg: #fdf3e6;
  --sev-med:  #8a6100;  --sev-med-bg:  #faf4e3;
  --sev-low:  #1b7a4b;  --sev-low-bg:  #eaf5ee;
  --sev-info: #5a5d62;  --sev-info-bg: #f1f1ef;
}
:root[data-theme="dark"], .skin-probe-dark {
  --sev-crit: #f0776b;  --sev-crit-bg: #2a1e1d;
  --sev-high: #eb9a4d;  --sev-high-bg: #2a2219;
  --sev-med:  #d9b649;  --sev-med-bg:  #28241a;
  --sev-low:  #5cbd85;  --sev-low-bg:  #1a2721;
  --sev-info: #a4aab3;  --sev-info-bg: #22262c;
}
/* Derived tints + legacy aliases — NEVER set by a skin, always computed. */
:root {
  --ion-weak:    color-mix(in srgb, var(--ion) 11%, transparent);
  --ion-glow:    color-mix(in srgb, var(--ion) 45%, transparent);
  --plasma-weak: color-mix(in srgb, var(--plasma) 12%, transparent);
  --plasma-glow: color-mix(in srgb, var(--plasma) 45%, transparent);
  --ok-weak:     color-mix(in srgb, var(--ok) 12%, transparent);
  --warn-weak:   color-mix(in srgb, var(--warn) 14%, transparent);
  --bad-weak:    color-mix(in srgb, var(--bad) 12%, transparent);
  /* The sidebar's own tints. They are WASHES (mixed into transparent) composited
     over --side, not mixes INTO it: --side is translucent under the cosmetic
     presets, and mixing into it turned ION's 11% wash into a dark chip (delta 1,
     Δ2). Over Slate's opaque shell the two are the same pixel.
     ⚠ A WASH NEVER STACKS (delta 2, D23). This holds for every translucent tint
     here — these two and --ion-weak / --plasma-weak / --ok-weak / --warn-weak /
     --bad-weak above: two nested elements carrying one composite it twice and the
     INNER one's ink fails. Exactly one element in a nesting chain carries the
     tint — the OUTER one, which is the one that names the condition. An inner
     element distinguishes itself with its border, its weight or its ink.
     (The --sev-*-bg grounds are opaque hexes, so they cannot stack.) */
  --side-accent-weak: color-mix(in srgb, var(--side-accent) 14%, transparent);
  --side-accent-glow: color-mix(in srgb, var(--side-accent) 45%, transparent);
  --side-crit-bg:     color-mix(in srgb, var(--side-crit) 16%, transparent);
  /* severity hairlines: one rule for all five tiers */
  --sev-crit-line: color-mix(in srgb, var(--sev-crit) 15%, var(--sev-crit-bg));
  --sev-high-line: color-mix(in srgb, var(--sev-high) 15%, var(--sev-high-bg));
  --sev-med-line:  color-mix(in srgb, var(--sev-med)  15%, var(--sev-med-bg));
  --sev-low-line:  color-mix(in srgb, var(--sev-low)  15%, var(--sev-low-bg));
  --sev-info-line: color-mix(in srgb, var(--sev-info) 15%, var(--sev-info-bg));
  /* legacy names used throughout the components below */
  --bg:            var(--void);
  --border:        var(--line);
  --border-strong: var(--line-strong);
  --accent:        var(--ion);
  --accent-weak:   var(--ion-weak);
  --radius: 8px;
  --radius-lg: 12px;
  --table-head-top: 48px;  /* sticky header offset = the top bar's height, set ONCE (5b D73; 6a D55) */
  --mono: ui-monospace, "Cascadia Code", "JetBrains Mono", Consolas, monospace;
  --sans: -apple-system, "Segoe UI", system-ui, Roboto, sans-serif;
}
/* Type scale — derived, never skinned. A skin picks colour, never size.
   ONE caps role: --fs-micro · sans · 600 · 0.5px tracking · --muted, for a label
   sitting directly above the value it names (.card .lbl, th, .info-grid .k,
   .pform label, .ai-grade .lbl). Nothing else is uppercase. */
:root {
  --fs-title:   24px;   /* h1, one per page                            */
  --fs-section: 18px;   /* a real subsection within a page             */
  --fs-head:    15px;   /* card, widget, panel and modal headings      */
  --fs-body:    14px;   /* prose, help text, modal bodies              */
  --fs-ui:      13px;   /* controls, table cells, nav — the workhorse  */
  --fs-meta:    12.5px; /* secondary line under a primary one          */
  --fs-micro:   11px;   /* badges, pills, and the ONE caps label role  */
  --fs-data:    12.5px; /* mono: hostnames, IPs, CVEs, versions, times */
  --fs-data-sm: 11px;   /* mono: inside a badge or a dense cell        */
}
/* One ring, everywhere it can be reached by keyboard. :where() zeroes the
   selector list, so the rule weighs only its :focus-visible (0,1,0) and sets
   nothing but the outline — component rules still own border and shadow, and
   any component that declares its own outline (th.sortable, .switch) wins. */
:root { --ring: var(--accent); }
:where(a[href], button, input, select, textarea, summary,
       [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
/* Inside a scrolling table head the offset clips; pull it inward. */
:where(th, td) :focus-visible, th.sortable:focus-visible { outline-offset: -2px; }
/* Same cure for a full-width stop sitting flush inside a scrolling box, where
   an outward ring loses its left and right edges (measured: 4px each side).
   The sidebar nav scrolls with no gutter; so does the Live Query target tree. */
#sidebar nav a:focus-visible, .qt-group > summary:focus-visible { outline-offset: -2px; }

html { background: var(--bg); }
body {
  background:
    radial-gradient(1100px 500px at 75% -10%, var(--tint1), transparent 60%),
    radial-gradient(900px 600px at -10% 30%, var(--tint2), transparent 55%),
    var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
/* Faint holo-grid over the whole canvas (fades out toward the bottom). */
body::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: 44px 44px;
  -webkit-mask-image: radial-gradient(ellipse at 40% 0%, black 30%, transparent 80%);
  mask-image: radial-gradient(ellipse at 40% 0%, black 30%, transparent 80%);
}
.layout { display: flex; min-height: 100vh; position: relative; z-index: 1; }
#sidebar {
  width: 232px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  /* Flat, in every skin. It used to darken toward the bottom by mixing the panel
     with a literal `black` — the FIRST of the two ION deltas this phase accepts
     on purpose. The SECOND (delta 1, D11): a cosmetic preset's --side-line is its
     own --line, so ION's sidebar hairlines are what they always were — but the nav
     scrollbar thumb, which used to read --border-strong, is now one step fainter.
     Decorative, and permitted; not a bug to file. */
  background: var(--side);
  border-right: 1px solid var(--side-line);
  padding: 20px 12px;
  position: sticky;
  top: 0;
  height: 100vh;
  backdrop-filter: blur(var(--blur));
  /* THE SHELL IS ITS OWN SURFACE. Under both Slate schemes it is dark while the
     content ground flips, so NOTHING inside it may read a content token
     (--text/--muted/--faint/--accent/--border/--panel2/--bad): under Slate Light
     those resolve to dark ink and land at 1.0–2.6:1 on this ground. Ink comes
     from the --side-* ramp. The focus ring follows it with one declaration,
     because custom properties inherit. */
  color: var(--side-text);
  --ring: var(--side-accent);
}
.brand {
  font-family: var(--sans); font-size: var(--fs-ui); font-weight: 600;
  letter-spacing: 0.2px;
  line-height: 1.3;
  color: var(--side-text);
  padding: 0 12px 18px;
  display: flex; flex-direction: column; align-items: center; gap: 9px;
}
/* Matches only the "Reticulum" span: .brand-name is a <div>, so wrapping the
   text did not widen this selector. */
.brand span { color: var(--side-accent); text-shadow: 0 0 calc(var(--glow) * 0.75) var(--side-accent-glow); }
.brand-name { text-align: center; }

/* Brand mark. The artwork is deliberately two-tone — a cyan half and a neutral
   half — and the neutral half is near-white (#e3e8f0), so it is invisible on
   a light ground. Hence two files swapped by ground rather than one.
   ⚠ The swap keys on :root[data-theme], which is what the sidebar toggle sets.
   A <picture media="(prefers-color-scheme:…)"> would follow the OS instead and
   ignore the in-app toggle completely, so it is deliberately not used here.
   ⚠ Being a raster, the mark does NOT follow a custom skin's accent the way the
   wordmark beside it does (live text on var(--side-accent)). Accepted tradeoff:
   the logo is a fixed brand asset, the wordmark stays skinnable.
   ⚠ THE SWAP IS SCOPED TO THE LOGIN BOX (2026-09-21). The sidebar is a dark
   surface in EVERY skin, Slate Light included, so its mark is always the
   dark-ground artwork — swapping it by data-theme would put the light-ground
   file on the dark shell. The login box sits on --panel, which IS white under
   Slate Light, so it keeps the swap. */
.qr-mark { display: block; align-self: center; width: auto; }
.qr-mark-light { display: none; }
.brand .qr-mark-dark  { display: block; }
.brand .qr-mark-light { display: none; }
:root[data-theme="light"] .login-box .qr-mark-dark  { display: none; }
:root[data-theme="light"] .login-box .qr-mark-light { display: block; }
.brand .qr-mark { height: 56px; }
.login-box .qr-mark { height: 76px; margin-bottom: 2px; }
/* nav scrolls within the sticky sidebar so long tab lists (and the footer
   below) stay reachable; min-height:0 lets a flex child actually shrink+scroll. */
#sidebar nav { display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; overflow-y: auto; min-height: 0; scrollbar-width: thin; scrollbar-color: var(--side-line) transparent; }
#sidebar nav a {
  color: var(--side-text2);
  text-decoration: none;
  font-family: var(--sans);
  font-size: var(--fs-ui);
  font-weight: 400;
  /* The links must not SHRINK, or the nav never scrolls: `overflow: hidden`
     below makes a flex item's min-height 0, so with the default flex-shrink the
     column squeezed 29 links into the space instead — measured at 1024x768: every
     link 14px tall, exactly its padding, its text clipped to nothing. */
  flex-shrink: 0;
  padding: 7px 12px 7px 16px;
  border-radius: 6px;
  border-left: 2px solid transparent;
  transition: background .15s, color .15s, border-color .15s;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#sidebar nav a:hover { color: var(--side-text); background: var(--side2); }
#sidebar nav a.active {
  color: var(--side-accent);
  background: var(--side-accent-weak);
  border-left-color: var(--side-accent);
  font-weight: 600;
  text-shadow: 0 0 calc(var(--glow) * 0.625) var(--side-accent-glow);
}
/* Nav sections (phase 5). The label is a caps micro line in the sidebar's own
   secondary ink; the group owns the spacing so the links keep their own box. */
#sidebar nav .nav-group { display: flex; flex-direction: column; margin-top: 18px; }
#sidebar nav .nav-grouplabel {
  font-family: var(--sans);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--side-text2);
  padding: 0 12px 6px;
  flex-shrink: 0;
}

/* The rail's handle (phase 7, D123 / D126): the sidebar's last control, after the provenance
   footer. It sits on the dark shell, so it reads --side-* ink, like the theme toggle did here. */
.rail-handle {
  align-self: flex-start; margin: 8px 12px 0; padding: 2px 10px;
  background: none; border: 1px solid var(--side-line); border-radius: var(--radius);
  color: var(--side-text2); font-family: var(--mono); font-size: var(--fs-ui); cursor: pointer;
}
.rail-handle:hover { color: var(--side-accent); border-color: var(--side-accent); }
@media (width <= 800px) { .rail-handle { display: none; } }   /* 7 D127: below 800px the drawer owns the sidebar; D132: range syntax, no gap */

/* Icon rail (phase 7). A state on <html> (D128), never a second nav: the same links, the same ids,
   the same role gate - just 56px of them. It exists only above 800px (D127). */
@media (width > 800px) {   /* 7 D132: with (width <= 800px) above, every width is covered once - no gap at a fractional viewport */
  :root[data-nav="rail"] #sidebar { width: 56px; padding: 20px 8px; }
  :root[data-nav="rail"] .brand-name,
  :root[data-nav="rail"] .nav-grouplabel,
  :root[data-nav="rail"] .sysinfo { display: none; }   /* D126: provenance is one click away */
  :root[data-nav="rail"] #sidebar nav a { position: relative; padding: 8px 0; text-align: center; overflow: visible; }
  /* D129: the name stays in the accessibility tree (visually hidden, not display: none) and the
     code is decorative generated content - so a screen reader hears "Endpoints", not "EP". */
  :root[data-nav="rail"] #sidebar nav a .navlabel {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
  }
  :root[data-nav="rail"] #sidebar nav a::before {
    content: attr(data-code);          /* fallback for engines without alt text */
    content: attr(data-code) / "";     /* the code is decorative; the name is the span */
    font-family: var(--mono); font-size: var(--fs-ui); font-weight: 600; letter-spacing: 0;
  }
  /* D118: the section labels become hairlines - the grouping is what keeps 29 codes navigable. */
  :root[data-nav="rail"] #sidebar nav .nav-group { margin-top: 10px; border-top: 1px solid var(--side-line); padding-top: 10px; }
  /* D131: every section keeps its hairline, the first included - Dashboard leads the nav, so no rule can. */
  /* D130: 56px cannot spare a scrollbar gutter (on Windows it took a quarter of the width and pushed the
     codes off the brand mark's centre). The nav still scrolls by wheel, touch and keyboard. */
  :root[data-nav="rail"] #sidebar nav { scrollbar-width: none; }
  :root[data-nav="rail"] #sidebar nav::-webkit-scrollbar { display: none; }
  :root[data-nav="rail"] .rail-handle { align-self: center; margin: 8px 0 0; }
}
/* main's padding lives in two variables so the top bar can cancel it in ONE rule (5b D93),
   and the width cap moved to #view so the bar spans the whole content column (5b D94).
   #view's content box is today's main content box at every width: min(column, 1320)
   less the padding either side. */
main { --main-pad-y: 28px; --main-pad-x: 32px; flex: 1; padding: var(--main-pad-y) var(--main-pad-x); min-width: 0; }
#view { max-width: calc(1320px - 2 * var(--main-pad-x)); }

/* Utility bar (phase 5b). Content-column width, sticky, one hairline. It is the
   scope indicator's host — see D63; the strip's own rules are unchanged. */
.topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 14px;
  height: 48px; margin: calc(-1 * var(--main-pad-y)) calc(-1 * var(--main-pad-x)) 22px; padding: 0 var(--main-pad-x);
  /* 5b D100 - 6a delta 1's D62, on the bar: --panel as a flat image layer over --bg, so a
     preset's translucent --panel never shows the content scrolling beneath it. */
  background-color: var(--bg);
  background-image: linear-gradient(var(--panel), var(--panel));
  border-bottom: 1px solid var(--border);
}
.topbar-scope { min-width: 0; flex: 0 1 auto; align-self: center; }
/* The strip moves WITH the ground it was measured on (5b D91): --side, only when a
   strip is there, so a role without one gets no box at all. */
.topbar-scope:not(:empty) { background: var(--side); border-radius: var(--radius-lg); }
.topbar-search {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 10px;
  min-width: 190px; padding: 6px 10px;
  background: var(--panel2); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--muted); font-family: var(--sans); font-size: var(--fs-ui);
  cursor: pointer; text-align: left;
}
.topbar-search:hover { border-color: var(--border-strong); color: var(--text); }
.topbar-search kbd {
  margin-left: auto; font-family: var(--mono); font-size: var(--fs-micro);
  color: var(--faint); border: 1px solid var(--border); border-radius: 4px; padding: 1px 5px;
}
.topbar-account { position: relative; display: flex; align-items: center; gap: 10px; }
.drawer-btn { display: none; }
body.signed-out .topbar { display: none; }   /* 5b D94: the login card is unchanged */

/* The account menu (5b D74): one button; its popover holds what the sidebar's .userbox
   held - the role, the profile link, the theme toggle and Sign out. It sits on --panel,
   so every --side-* read maps to its content equivalent here (5b D92). */
.acct-btn {
  display: inline-flex; align-items: center; padding: 6px 10px;
  background: none; border: 1px solid var(--border); border-radius: var(--radius-lg);
  color: var(--text); font-family: var(--sans); font-size: var(--fs-ui); cursor: pointer;
}
.acct-btn:hover, .acct-btn[aria-expanded="true"] { border-color: var(--border-strong); }
.acct-pop {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 41;
  display: flex; flex-direction: column; align-items: flex-start; gap: 10px;
  min-width: 200px; padding: 12px 16px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: 0 14px 48px var(--shadow), 0 2px 8px var(--shadow);
  font-size: var(--fs-ui);
}
.acct-pop[hidden] { display: none; }
.topbar-account .theme-toggle { background: var(--panel2); border-color: var(--border); color: var(--muted); }
.topbar-account .theme-toggle:hover { color: var(--accent); border-color: var(--accent); box-shadow: none; }

/* The command palette (5b D65-D69): the nav's own routes, endpoints by hostname, and
   (after 6b) saved views. Not a kit modal - it owns its keyboard - but it wears the
   kit's geometry: --scrim behind, --panel, --border, --radius-lg, the modal's shadow. */
.palette-overlay {
  position: fixed; inset: 0; z-index: 210;
  display: flex; justify-content: center; align-items: flex-start; padding: 12vh 24px 24px;
  background: var(--scrim); backdrop-filter: blur(calc(var(--blur) * 0.375));
}
.palette {
  width: min(640px, 94vw); max-height: 70vh; display: flex; flex-direction: column; overflow: hidden;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: 0 14px 48px var(--shadow), 0 2px 8px var(--shadow);
}
.palette input.palette-input {
  width: 100%; max-width: none; padding: 12px 16px;
  background: transparent; border: 0; border-bottom: 1px solid var(--border); border-radius: 0;
  font-size: var(--fs-body);
}
.palette-list { overflow: auto; padding: 4px 0 10px; }
/* Group headings take the sheet's ONE caps role (5b D98 via D67). */
.palette-head {
  padding: 10px 16px 4px;
  font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.5px;
  text-transform: uppercase; color: var(--muted);
}
.palette-item { display: flex; align-items: baseline; gap: 10px; padding: 7px 16px; color: var(--text); cursor: pointer; }
.palette-item .muted { font-size: var(--fs-meta); }
.palette-item .mono { font-family: var(--mono); font-size: var(--fs-data); }
.palette-item[aria-selected="true"] { background: var(--accent-weak); }
.palette-note { padding: 4px 16px 6px; color: var(--muted); font-size: var(--fs-meta); }

/* Mobile: the sidebar becomes an off-canvas drawer (5b D75) - the same nav in its full
   vertical form (brand, sections, .sysinfo) over a scrim. It is inert while closed
   (app.js syncDrawer) and closes on every route change. */
.drawer-scrim { display: none; position: fixed; inset: 0; z-index: 55; background: var(--scrim); }
@media (max-width: 800px) {
  #sidebar { position: fixed; inset: 0 auto 0 0; width: 264px; z-index: 60;
    transform: translateX(-100%); transition: transform .2s; }
  .layout.drawer-open #sidebar { transform: translateX(0); }
  .layout.drawer-open .drawer-scrim { display: block; }
  .drawer-btn { display: inline-flex; }
  .topbar-search { min-width: 0; }
}
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

h1 { font-family: var(--sans); font-size: var(--fs-title); font-weight: 600; margin-bottom: 18px; letter-spacing: -0.2px; }
h2 { font-family: var(--sans); font-size: var(--fs-head); font-weight: 600; margin: 22px 0 12px; color: var(--text); }

/* Stat tiles. The glass, the HUD corner tick and the glow on a hot number are
   all the SKIN's: --blur, --tick and --glow are lengths a skin sets, so under
   Slate (0px each) the tile is a flat card and under ION it is what it was
   (law 5 — liveness is signalled, the signal is per-skin). */
.cards { display: flex; gap: 14px; margin-bottom: 22px; flex-wrap: wrap; }
.card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 16px 22px;
  min-width: 140px;
  position: relative;
  overflow: hidden;
  backdrop-filter: blur(calc(var(--blur) * 0.75));
}
.card::after {
  content: ""; position: absolute; top: 0; right: 0; width: 20px; height: 20px;
  border-top: var(--tick) solid var(--border-strong); border-right: var(--tick) solid var(--border-strong);
  border-radius: 0 var(--radius-lg) 0 0;
}
.card .num { font-family: var(--mono); font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums; }
.card .lbl { color: var(--muted); font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; margin-top: 2px; }
/* .ok/.warn/.bad count ENTITIES IN A STATE (online, updating, offline). A tile
   counting RANKED FINDINGS takes .sev-* instead — see the severity primitive. */
.card.ok .num { color: var(--ok); text-shadow: 0 0 var(--glow) var(--ok-weak); }
.card.ok::after { border-color: var(--ok); }
.card.bad .num { color: var(--bad); text-shadow: 0 0 var(--glow) var(--bad-weak); }
.card.bad::after { border-color: var(--bad); }
/* Matches the "updating" status badge (.badge.updated → var(--warn)) so the
   Endpoints filter tile and the status column read as the same colour. */
.card.warn .num { color: var(--warn); text-shadow: 0 0 var(--glow) var(--warn-weak); }
.card.warn::after { border-color: var(--warn); }

/* Filterable counter tiles (Endpoints page). What's interactive must LOOK
   interactive (law 2: ion = the one interactive colour); the active lens glows
   so "why am I only seeing 3 endpoints?" is answerable at a glance. */
.card.filterable {
  cursor: pointer; text-align: left; font: inherit; color: inherit;
  transition: border-color .15s, box-shadow .15s, transform .08s;
}
.card.filterable:hover { border-color: var(--accent); }
.card.filterable:active { transform: translateY(1px); }
.card.filterable.active {
  border-color: var(--accent);
  box-shadow: 0 0 var(--glow) var(--ion-glow), inset 0 0 calc(var(--glow) * 1.625) var(--ion-weak);
}
.card.filterable.active::after { border-color: var(--accent); }
.card.filterable.active .lbl { color: var(--accent); }

/* No overflow: hidden here (6a D54): it made the table its own scroll container, so a
   sticky th stuck to a box that never scrolls. The corners are cut on the four outer
   cells instead; where a table's top is square (details.grp) so are its top cells. */
table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); backdrop-filter: blur(calc(var(--blur) * 0.75)); }
thead tr:first-child th:first-child { border-top-left-radius: var(--radius-lg); }
thead tr:first-child th:last-child  { border-top-right-radius: var(--radius-lg); }
tbody tr:last-child td:first-child  { border-bottom-left-radius: var(--radius-lg); }
tbody tr:last-child td:last-child   { border-bottom-right-radius: var(--radius-lg); }
/* Two densities (6a D57). Default: a table a person reads a page of. Compact: machine
   strings in many columns, scanned not read - dataset tables on a detail page take it
   by role (the class is on their <table>); phase 6c lets a user set it per screen. */
th, td { text-align: left; padding: 11px 16px; border-bottom: 1px solid var(--border); }
table.compact th, table.compact td { padding: 6px 10px; }
table.compact td { font-size: var(--fs-meta); }
/* The caps label role. --muted, not --faint: at this size faint is the wrong
   ink for a column name (muted on panel2 measures 6.33 light / 6.50 dark). */
th { background-color: var(--bg); background-image: linear-gradient(var(--panel2), var(--panel2)); color: var(--muted); font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; border-bottom: 1px solid var(--border-strong);
  position: sticky; top: var(--table-head-top, 0px); z-index: 2; }   /* 6a D55 sticky; delta 1 D62: --panel2 is composited onto --bg, so the pinned header is opaque even where a preset's --panel2 carries alpha - no preset is edited */
tr:last-child td { border-bottom: none; }
tbody tr { transition: background .12s; }
tbody tr.click { cursor: pointer; }
/* A checked .selcol row is --accent-weak (6a D59, made true by delta 1 D64): the checkbox is
   the state, so no class mirrors it. :where() zeroes the :has() to (0,0,2) (delta 2 D67), so
   every hover rule and any classed row rule outweighs it whatever the order - selection is the
   weakest row ground on purpose, and D64's ordering note is struck. */
tbody tr:where(:has(.selcol input:checked)) { background: var(--accent-weak); }
/* Hover is a neutral ground (6a D59): in a .click table every row is interactive, so an
   accent wash said nothing the pointer did not - and on Slate Light it read as selected.
   --accent-weak stays with selection. */
tbody tr.click:hover { background: var(--panel2); }
/* One empty state (6a D60): <tr><td class="empty" colspan="N">…</td></tr>. The sentence is
   the view's own - what is absent and why, never "No data". No view is converted by 6a. */
td.empty { color: var(--muted); font-family: var(--sans); font-size: var(--fs-body); text-align: center; padding: 28px 16px; }

/* Badges: outcome/state. A hairline so they read as chips, not blobs. Status
   colors keep their meaning in every skin (law 4). Sentence case: the one caps
   role is a label ABOVE a value, and a badge is the value. */
.badge {
  display: inline-block; padding: 2px 10px; border-radius: 20px;
  font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600;
  letter-spacing: 0.3px; white-space: nowrap;
}
/* One outcome vocabulary: ok / warn / bad, plus the bare badge for a fact that is
   not an outcome. The names after each canonical class are ALIASES kept so the
   existing call sites keep rendering — new code writes ok, warn, bad or nothing.
   Severity is a DIFFERENT vocabulary and stays on phase 1's --sev-* (D78). */
.badge.ok,   .badge.online,  .badge.added,   .badge.done,  .badge.success, .badge.found
  { background: var(--ok-weak);   color: var(--ok);   border: 1px solid color-mix(in srgb, var(--ok) 40%, transparent); }
.badge.bad,  .badge.offline, .badge.removed, .badge.error, .badge.failed
  { background: var(--bad-weak);  color: var(--bad);  border: 1px solid color-mix(in srgb, var(--bad) 40%, transparent); }
.badge.warn, .badge.updated, .badge.pending, .badge.sent, .badge.unknown
  { background: var(--warn-weak); color: var(--warn); border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent); }
/* A BADGE'S WASH NEVER STACKS (D412, Slate design's 111; delta 2's D23). In a table, an outcome badge's ground is its wash mixed
   INTO --panel-solid, the opaque ground of the table it sits in, so it is opaque: a selected row's --accent-weak under it no longer
   shows through and lowers its ink (the offline badge on a selected row read 4.02-4.42:1 in five pairs). The percentages are
   --ok-weak's, --bad-weak's and --warn-weak's, so on a plain row the pixel is the one the translucent wash made. An opaque colour,
   not an image layer: every ground the drivers composite stays readable.
   Outside a table the badge keeps its translucent wash - HELD at D412's stop: an opaque ground there moved passing badges by
   more than 0.3:1 (the status legends on #/tasks, #/events and #/deploy sit on the page ground; replies/112), and no badge
   outside a table sits on a tint in the walk. */
table .badge:is(.ok, .online, .added, .done, .success, .found) { background: color-mix(in srgb, var(--ok) 12%, var(--panel-solid)); }
table .badge:is(.bad, .offline, .removed, .error, .failed)      { background: color-mix(in srgb, var(--bad) 12%, var(--panel-solid)); }
table .badge:is(.warn, .updated, .pending, .sent, .unknown)     { background: color-mix(in srgb, var(--warn) 14%, var(--panel-solid)); }

/* Severity primitive — the ONLY way severity is expressed. Five tiers, three
   forms. A status class (.online/.offline/.done/.failed/.pending/.updated)
   never carries a severity and a severity class never carries a status.
   Emitted by ONE function, sevBadge() in app.js; nothing else picks a tier.
   ⚠ The full `border` is declared here, not just border-color: the base .badge
   has NO border of its own — each status variant above brings its own — so a
   bare border-color would paint nothing. */
.badge.sev-crit { color: var(--sev-crit); background: var(--sev-crit-bg); border: 1px solid var(--sev-crit-line); }
.badge.sev-high { color: var(--sev-high); background: var(--sev-high-bg); border: 1px solid var(--sev-high-line); }
.badge.sev-med  { color: var(--sev-med);  background: var(--sev-med-bg);  border: 1px solid var(--sev-med-line); }
.badge.sev-low  { color: var(--sev-low);  background: var(--sev-low-bg);  border: 1px solid var(--sev-low-line); }
.badge.sev-info { color: var(--sev-info); background: var(--sev-info-bg); border: 1px solid var(--sev-info-line); }

/* Stat tile: a tile counting RANKED findings, not entities in a state. */
.card.sev-crit .num { color: var(--sev-crit); text-shadow: 0 0 var(--glow) var(--sev-crit-bg); }
.card.sev-crit::after { border-color: var(--sev-crit); }
.card.sev-high .num { color: var(--sev-high); text-shadow: 0 0 var(--glow) var(--sev-high-bg); }
.card.sev-high::after { border-color: var(--sev-high); }
.card.sev-med .num { color: var(--sev-med); text-shadow: 0 0 var(--glow) var(--sev-med-bg); }
.card.sev-med::after { border-color: var(--sev-med); }
.card.sev-low .num { color: var(--sev-low); text-shadow: 0 0 var(--glow) var(--sev-low-bg); }
.card.sev-low::after { border-color: var(--sev-low); }
.card.sev-info .num { color: var(--sev-info); text-shadow: 0 0 var(--glow) var(--sev-info-bg); }
.card.sev-info::after { border-color: var(--sev-info); }

/* Bare text: a table cell or fact-grid value with no chrome of its own. The
   prefix is why this is safe — a bare .crit would collide with the first
   component that wanted one. */
.sev-crit { color: var(--sev-crit); }
.sev-high { color: var(--sev-high); }
.sev-med  { color: var(--sev-med); }
.sev-low  { color: var(--sev-low); }
.sev-info { color: var(--sev-info); }

.muted { color: var(--muted); }
/* D344 (Slate design's 069): the bare `warn` class is warning-coloured text. Zero specificity, so every compound rule
   (.badge.warn, .cond-cov-row.warn, .card.filterable) still wins; the :not() keeps the containers that carry the class -
   the .notice.warn banners and the card warn tiles - from passing the colour to their children. */
:where(.warn:not(.notice, .card)) { color: var(--warn); }
.notice { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px 18px; margin: 14px 0; backdrop-filter: blur(calc(var(--blur) * 0.75)); }
/* Notice callout — prose that states a condition, with no number in it. Variants
   tint the ground and colour the hairline; the CUE IS THE SENTENCE. Every call
   site opens with a <b> that names the condition in words, and colour is the
   second signal, never the only one (amended law 5). A notice carrying a number
   is a stat tile and belongs in .card. */
.notice.ok   { background: var(--ok-weak);   border-color: color-mix(in srgb, var(--ok) 40%, transparent); }
.notice.warn { background: var(--warn-weak); border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.notice.bad  { background: var(--bad-weak);  border-color: color-mix(in srgb, var(--bad) 40%, transparent); border-width: 2px; padding: 15px 17px; }
/* kitNotice (Slate design's 064, D334): the one refusal slot a view carries under its toolbar - the kit's .notice.bad with
   room for its close control, which borrows the modal's close control (.modal-close) token for token. */
.notice.bad.kit-notice { position: relative; padding-right: 48px; }
.kit-notice-x { position: absolute; top: 10px; right: 10px; padding: 2px 9px; cursor: pointer; border-radius: 5px;
  background: none; border: 1px solid var(--line); color: var(--muted); font-family: var(--mono); font-size: var(--fs-ui); }
.kit-notice-x:hover { color: var(--ion); border-color: var(--ion); }
.selcol { width: 1%; text-align: center; white-space: nowrap; }
.selcol input { cursor: pointer; accent-color: var(--accent); }
.bulkbar { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%); z-index: 50;
  display: flex; align-items: center; gap: 10px; padding: 10px 14px;
  background: var(--panel); border: 1px solid var(--accent); border-radius: var(--radius-lg);
  backdrop-filter: blur(calc(var(--blur) * 1.25));
  box-shadow: 0 8px 28px var(--shadow), 0 0 calc(var(--glow) * 1.125) var(--ion-weak); }
.bulkbar[hidden] { display: none; }
.bulkbar #bulkcount { font-family: var(--mono); font-weight: 700; color: var(--accent); margin-right: 4px; }
.bulkbar button { padding: 6px 12px; }
.bulkbar a { color: var(--muted); font-family: var(--mono); font-size: 11px; }
.bulkbar a:hover { color: var(--accent); }
.mono { font-family: var(--mono); font-size: 12.5px; font-variant-numeric: tabular-nums; }

/* 6f-2 D178 (D290): a password input takes the kit look the text inputs have, in every context without its own rule. */
input[type=text], input[type=number], input[type=password] {
  background: var(--panel2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  padding: 9px 13px;
  width: 340px;
  font-size: 14px;
  transition: border-color .12s, box-shadow .12s;
}
/* Number inputs carry their own inline width (pid, delay, minutes) — don't force 340px. */
input[type=number] { width: auto; }
input[type=text]::placeholder, input[type=number]::placeholder, input[type=password]::placeholder { color: var(--faint); }
input[type=text]:focus, input[type=number]:focus, input[type=password]:focus { border-color: var(--accent); box-shadow: 0 0 calc(var(--glow) * 0.75) var(--ion-glow); }
input[type=checkbox], input[type=radio] { accent-color: var(--accent); }
select { color-scheme: inherit; }

/* Searchable combobox — a themed replacement for native <select> (the OS
   renders native option popups, so they can never follow the skin). Tokens
   only; composes the kit's input styling. */
.combo { position: relative; display: inline-block; }
.combo input[type=text] { width: 220px; padding: 7px 28px 7px 11px; font-size: 13px; }
.combo-caret { position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  color: var(--faint); pointer-events: none; font-size: var(--fs-micro); }
.combo-list { position: absolute; top: calc(100% + 4px); left: 0; z-index: 150; min-width: 100%;
  max-height: 280px; overflow-y: auto; background: var(--panel); backdrop-filter: blur(var(--blur));
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  box-shadow: 0 10px 30px var(--shadow); }
.combo-item { padding: 7px 12px; cursor: pointer; font-size: 13px; white-space: nowrap; }
.combo-item:hover, .combo-item.active {
  background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--ion); }
.combo-item.selected { color: var(--accent); }
/* A pick-list item's second line (D312, Slate design's 055): the kit's muted item detail - .muted's colour at the
   secondary-line size. The row itself is nowrap, so the detail wraps on its own. */
.combo-item .combo-detail { font-size: var(--fs-meta); white-space: normal; margin-top: 2px; }
.combo-empty { padding: 7px 12px; color: var(--faint); font-size: 13px; }
/* Modal pick-lists reuse .combo-item rows inside a bordered scroll panel. */
.picklist { border: 1px solid var(--border-strong); border-radius: var(--radius);
  max-height: 320px; overflow-y: auto; }

/* Dashboard customize list: a switch on the left, reorder arrows on the right.
   Tokens only — it inherits the active skin like every other kit surface. */
#dashList li { display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 8px 12px; border-bottom: 1px solid var(--border); }
#dashList li:last-child { border-bottom: 0; }
.dashmove { display: inline-flex; gap: 10px; flex: 0 0 auto; }
.dashmove a { text-decoration: none; color: var(--muted); font-size: var(--fs-ui); }
.dashmove a:hover { color: var(--ion); }

/* auto-fit rather than a fixed column count: there are three charts now and a
   hardcoded 1fr 1fr leaves the third alone on its own row. This flows for two,
   three or a later fourth without another breakpoint. */
.charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; margin-bottom: 8px; }
.chart-box { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px; height: 264px; }
.chart-box h3 { font-family: var(--sans); font-size: var(--fs-head); font-weight: 600; color: var(--text); margin-bottom: 10px; }
.chart-box canvas { max-height: 210px; }
@media (max-width: 900px) { .charts { grid-template-columns: 1fr; } }

.info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 12px 24px;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 18px 22px;
  margin-bottom: 22px;
}
.info-grid .k { color: var(--muted); font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
.info-grid .v { margin-top: 3px; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
a.back { display: inline-block; margin-bottom: 14px; font-family: var(--sans); font-size: var(--fs-ui); color: var(--muted); }
a.back:hover { text-decoration: none; color: var(--accent); text-shadow: 0 0 calc(var(--glow) * 0.5) var(--ion-glow); }
details { margin-top: 8px; }
summary { cursor: pointer; color: var(--muted); padding: 6px 0; }
.toolbar { display: flex; align-items: center; gap: 14px; margin: 14px 0; flex-wrap: wrap; }
.count { color: var(--accent); font-family: var(--mono); font-size: 11px; font-weight: 400; letter-spacing: .5px; }

/* The view bar (Slate 6b D109): one 36px row between the filter input and the table, no ground of its own - state on
   the left, controls on the right, the gap between them margin-left:auto because the left group's width follows the
   view's name. Geometry and inks are design's "Slate Shell" drawing in the kit's tokens: the picker and Save on
   --border-strong, Save as… and Columns on --border, Save in the accent, "· modified" in --warn mono. Not sticky:
   it scrolls away and the table header pins under the top bar (--table-head-top is 6a's and untouched). */
.viewbar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; min-height: 36px; margin: 0 0 10px; }
.viewbar .vb-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.vb-anchor { position: relative; display: inline-flex; }
.vb-btn {
  display: inline-flex; align-items: center; gap: 8px; padding: 5px 12px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  color: var(--muted); font-family: var(--sans); font-size: var(--fs-ui); line-height: 1.4; cursor: pointer;
}
.vb-btn:hover, .vb-btn[aria-expanded="true"] { border-color: var(--border-strong); color: var(--text); }
.vb-btn.vb-pick { padding: 5px 10px; border-color: var(--border-strong); color: var(--text); }
.vb-btn.vb-save { border-color: var(--border-strong); color: var(--accent); }
.vb-btn:disabled { color: var(--faint); border-color: var(--border); cursor: default; }
.vb-caret { color: var(--faint); font-size: 11px; }
.vb-mod { color: var(--warn); font-family: var(--mono); font-size: 11px; }
.vb-mod[hidden] { display: none; }
.vb-status { color: var(--muted); font-size: var(--fs-ui); }
.vb-status.vb-err { color: var(--bad); }
/* The density control (6c D113): Comfortable | Compact, one bordered group of two radio buttons. The checked segment
   carries the accent ink on an 8% accent wash; the other is --muted. It styles the CONTROL only - no rule here touches
   a table: 6a's `table.compact` is still the whole density vocabulary. No overflow:hidden on the group, so the kit's
   focus ring (2px outside) is never clipped; the segments round their own outer corners instead. */
.vb-seg { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel); }
.vb-seg-btn {
  padding: 5px 11px; background: none; border: 0; color: var(--muted);
  font-family: var(--sans); font-size: var(--fs-ui); line-height: 1.4; cursor: pointer;
}
.vb-seg-btn:first-child { border-radius: calc(var(--radius) - 1px) 0 0 calc(var(--radius) - 1px); }
.vb-seg-btn:last-child { border-radius: 0 calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0; border-left: 1px solid var(--border); }
.vb-seg-btn:hover { color: var(--text); }
.vb-seg-btn[aria-checked="true"] { color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--panel)); }
/* D297: Endpoints' one Rows-per-page control, after Density - the pager's label and the kit combo, at the view bar's own
   height (its buttons' padding, type size and line height). */
.vb-size { display: inline-flex; align-items: center; gap: 6px; }
.vb-size-label { color: var(--muted); font-size: var(--fs-ui); }
.vb-size .combo input[type=text] { width: 72px; padding: 5px 26px 5px 10px; font-size: var(--fs-ui); line-height: 1.4; }

/* The view bar's pickers (6b D110): anchored popovers, never modals - a pick shows its result at once. They wear the
   account menu's surface (5b D74) and share its layer, 41. .popmenu is also what layerOpen() counts (Q8). */
.popmenu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 41;
  display: flex; flex-direction: column; gap: 2px; min-width: 220px; max-width: min(360px, 90vw); max-height: 60vh;
  overflow-y: auto; padding: 8px;
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: 0 14px 48px var(--shadow), 0 2px 8px var(--shadow);
  font-size: var(--fs-ui);
}
.popmenu[hidden] { display: none; }
.popmenu.colpick { left: auto; right: 0; }
.popmenu .pm-row { display: flex; align-items: center; gap: 2px; }
.popmenu .pm-item {
  flex: 1; display: block; width: 100%; padding: 6px 8px; text-align: left;
  background: none; border: 0; border-radius: var(--radius); color: var(--text); font: inherit; cursor: pointer;
}
.popmenu .pm-item:hover { background: var(--panel2); }
.popmenu .pm-item[aria-current="true"] { color: var(--accent); }
.popmenu .pm-hint { color: var(--faint); font-size: 11px; margin-left: 6px; }
.popmenu .pm-del { padding: 4px 8px; background: none; border: 0; border-radius: var(--radius); color: var(--faint); cursor: pointer; }
.popmenu .pm-del:hover { color: var(--bad); }
.popmenu .pm-check { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: var(--radius); color: var(--text); cursor: pointer; }
.popmenu .pm-check:hover { background: var(--panel2); }
.popmenu .pm-check:has(input:disabled) { color: var(--faint); cursor: default; }
.popmenu .pm-foot { margin-top: 4px; border-top: 1px solid var(--border); border-radius: 0; padding-top: 8px; color: var(--muted); }
.popmenu .pm-note { margin: 4px 0 0; padding: 6px 8px 2px; border-top: 1px solid var(--border); color: var(--muted); }
.popmenu .pm-err { color: var(--bad); }

/* Live query — the SQL box is a terminal: console ground in every skin, ion caret. */
textarea.sql {
  background: var(--console);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--console-text);
  caret-color: var(--accent);
  padding: 12px 14px;
  width: 100%;
  min-height: 92px;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.7;
  resize: vertical;
  box-shadow: inset 0 0 calc(var(--glow) * 1.875) color-mix(in srgb, var(--accent) 4%, transparent);
  transition: border-color .12s, box-shadow .12s;
}
/* Console ground ⇒ console ink. --faint FLIPS with the scheme and the console
   never does: under Slate Light it put #6c7076 on #101828, 3.56:1 (delta 1, D10). */
textarea.sql::placeholder { color: var(--console-faint); }
textarea.sql:focus { border-color: var(--accent); box-shadow: 0 0 calc(var(--glow) * 0.875) var(--ion-glow), inset 0 0 calc(var(--glow) * 1.875) color-mix(in srgb, var(--accent) 5%, transparent); }
button.run {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 25%, transparent), var(--accent-weak));
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  color: var(--accent);
  font-family: var(--mono);
  font-weight: 600;
  font-size: var(--fs-ui);
  letter-spacing: 0.5px;
  padding: 9px 20px;
  cursor: pointer;
  text-shadow: 0 0 calc(var(--glow) * 0.5) var(--ion-glow);
  transition: box-shadow .15s;
}
button.run:hover { box-shadow: 0 0 calc(var(--glow) * 0.875) var(--ion-glow); }
button.run:disabled { opacity: .5; cursor: default; box-shadow: none; }
.qerr { color: var(--bad); font-family: var(--mono); font-size: var(--fs-data); white-space: pre-wrap; text-shadow: 0 0 calc(var(--glow) * 0.5) var(--bad-weak); }
.targets { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0; }
.results-wrap { overflow-x: auto; }

/* Live-query: one collapsible table per endpoint */
.qtools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 6px 0 12px; }
details.qhost { margin-bottom: 10px; }
details.qhost > summary {
  background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 9px 14px; display: flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: var(--fs-ui); font-weight: 600; color: var(--text);
}
details.qhost > summary:hover { border-color: var(--border-strong); }
details.qhost[open] > summary { border-radius: var(--radius) var(--radius) 0 0; border-bottom: none; }
.qhost-body {
  background: var(--panel); border: 1px solid var(--border); border-top: none;
  border-radius: 0 0 var(--radius) var(--radius); padding: 12px 14px;
}
.qhost-body table { border-radius: var(--radius); }
input.qfilter { width: 280px; max-width: 100%; padding: 7px 11px; font-size: 13px; margin-bottom: 10px; }
/* Sortable headers — every table in the app uses these (app.js sortHead). They
   are keyboard-reachable (tabindex) and carry aria-sort, so the focus ring must
   be visible: a header that can be operated must look like it. */
th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
th.sortable:hover { color: var(--text); }
th.sortable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* Sidebar footer: server + recommended agent versions, on the dark shell — --side-*
   ink only. The theme toggle and the account box moved to the top bar (5b), where
   they take content inks (D92). */
.sysinfo {
  margin-top: auto;
  padding: 12px 12px 4px;
  border-top: 1px solid var(--side-line);
  color: var(--side-text2);
  font-family: var(--sans);
  font-size: var(--fs-micro);
  letter-spacing: .2px;
  line-height: 1.9;
}
.sysinfo .mono { font-size: var(--fs-data-sm); color: var(--side-text); }

.theme-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--side2); border: 1px solid var(--side-line);
  color: var(--side-text2); border-radius: 20px;
  padding: 5px 12px; font-family: var(--sans); font-size: var(--fs-micro);
  cursor: pointer;
  transition: color .12s, border-color .12s, box-shadow .12s;
}
/* The hover halo is the accent at 11% — what --ion-weak was — but mixed from the
   SIDEBAR's accent, so the rule reads no content token. (--side-accent-weak is
   the wrong tool here: it is the 14% active-row wash, and this halo keeps 11%.) */
.theme-toggle:hover { color: var(--side-accent); border-color: var(--side-accent); box-shadow: 0 0 calc(var(--glow) * 0.625) color-mix(in srgb, var(--side-accent) 11%, transparent); }

/* Auth - the account box is the top bar's account menu now (5b D74), above. */
/* Signed out: center the login card in the full viewport (main otherwise caps
   at max-width and left-aligns, which pushes the card off-center on wide screens). */
body.signed-out main { max-width: none; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
body.signed-out .login-box { margin: 0; }
/* 5b D94: signed out, #view adds no box, so the login card stays main's own flex item and
   is centred and sized exactly as before (as a shrink-to-fit flex item #view collapsed the
   card's width: 100% to 285px - measured). */
body.signed-out #view { display: contents; }
.login-box {
  width: 100%;
  max-width: 380px;
  margin: 14vh auto 0;
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: 34px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  text-align: center;
  backdrop-filter: blur(calc(var(--blur) * 1.25));
  box-shadow: 0 0 calc(var(--glow) * 2.5) var(--ion-weak), inset 0 0 calc(var(--glow) * 3.75) color-mix(in srgb, var(--accent) 3%, transparent);
}
.login-box input { width: 100%; }
.login-box input[type=password] {
  background: var(--panel2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  padding: 9px 13px;
  font-size: 14px;
  transition: border-color .12s, box-shadow .12s;
}
.login-box input[type=password]:focus { border-color: var(--accent); box-shadow: 0 0 calc(var(--glow) * 0.75) var(--ion-glow); }
.login-box input[type=text] { width: 100%; }
select.rolesel {
  background: var(--panel2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  padding: 9px 13px;
  font-size: 14px;
}
/* Toolbar filter dropdowns (Events kind/status, Action Tasks state/type) —
   themed to match the panel instead of the raw OS <select> chrome. A custom
   chevron keeps it consistent across light/dark. */
.toolbar select {
  background: var(--panel2);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--text);
  padding: 9px 34px 9px 13px;
  font-size: 14px;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                    linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: right 15px center, right 10px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.toolbar select:focus-visible { border-color: var(--accent); }
.toolbar select option { background: var(--panel2); color: var(--text); }

/* Endpoint deployment */
a.run.dl { text-decoration: none; display: inline-block; }
a.run { background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 25%, transparent), var(--accent-weak)); border: 1px solid var(--accent); border-radius: var(--radius); color: var(--accent); font-family: var(--mono); font-weight: 600; font-size: var(--fs-ui); letter-spacing: 0.5px; padding: 8px 16px; cursor: pointer; text-shadow: 0 0 calc(var(--glow) * 0.5) var(--ion-glow); transition: box-shadow .15s; }
a.run:hover { box-shadow: 0 0 calc(var(--glow) * 0.875) var(--ion-glow); text-decoration: none; }

/* User presence dots */
/* Presence dots: only LIVE things glow + pulse (law 5). */
.presence { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 8px; vertical-align: baseline; }
.presence.on { background: var(--ok); box-shadow: 0 0 calc(var(--glow) * 0.5) var(--ok); animation: presence-pulse 2.4s ease-in-out infinite; }
.presence.off { background: var(--faint); }
@keyframes presence-pulse { 50% { opacity: .45; } }

/* Dashboard widgets. Phase 8: every body is one of two patterns - A, the ranked row list (.vrow), or B,
   the labelled bar list (.wbars) - and the AI widget keeps its plasma body (law 3). No chart canvas and
   no stat tile lives inside a widget: a tile inside a card is a card inside a card. */
.dashhead { display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 18px; }
.dashhead h1 { margin-bottom: 0px; }   /* 8 D132: the h1 and the customize button share one row */
.widgets { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 16px; }
.widget { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px 20px; }
.widget h3 { font-family: var(--sans); font-size: var(--fs-head); font-weight: 600; color: var(--text); margin-bottom: 14px;
  display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 8px; row-gap: 2px; }
.widget h3 a { font-weight: 400; }
/* 8 D130 / D138: the link to the screen that owns the data sits at the heading's far end, so it never reads as
   part of a count beside the title ("2" problems, then "Events" - not "2 Events"). */
.widget h3 > a:last-child { margin-left: auto; }
/* D350 (Slate design's 079): the Current Problems title links to #/problems. It keeps the title's own ink and weight - the
   global `a` rule and the one above would give it --accent at 400 - and the global a:hover underline is the kit link's hover. */
.widget h3 a.cond-title { color: inherit; font-weight: inherit; }
.widget.placeholder { border-style: dashed; opacity: .8; }
.vrow { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); font-family: var(--mono); font-size: var(--fs-data-sm); font-variant-numeric: tabular-nums; }
.vrow:last-child { border-bottom: none; }
.vrow.click { cursor: pointer; border-radius: 6px; margin: 0 -8px; padding: 8px; }
.vrow.click:hover { background: var(--accent-weak); }
/* A Current Problems row (Slate design's 063, D329) - its own class, so the other widgets' .vrow rows are untouched. A
   two-column grid, minmax(0,1fr) auto: the first column may shrink below its content, so a long ref WRAPS instead of widening
   its line over the ones beside it. Line 1 the host and the kind, the age on the right; line 2 the ref, whole; line 3 the
   detail; line 4 a process.unknown row's verdict controls. The mock's spacing: 11px vertical padding, 3px between lines, the
   kit border between rows. */
.cond-row { display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 12px; row-gap: 3px; padding: 11px 0; overflow-wrap: anywhere; }
.cond-row + .cond-row { border-top: 1px solid var(--border); }
.cond-host { min-width: 0; overflow-wrap: anywhere; font-family: var(--sans); font-size: var(--fs-ui); font-weight: 600; }
.cond-age { font-family: var(--mono); font-size: var(--fs-data-sm); color: var(--muted); white-space: nowrap; }
/* D343 (Slate design's 069): the kind badge and the age wrap too - 068 §1 ("every text child wraps") over §4 for a long
   word. On real data it moves no box: every .cond-row element's rect measured identical. */
.cond-host .badge, .cond-age { white-space: normal; }
/* D348 (Slate design's 071): the age breaks a long word by its own rule too, so the row's rule and the age's each cover the
   other, as 068 intended; the row's rule stays for children added later. */
.cond-age { overflow-wrap: anywhere; }
.cond-ref { grid-column: 1 / -1; min-width: 0; overflow-wrap: anywhere; font-family: var(--mono); font-size: var(--fs-data); color: var(--text); }
.cond-detail { grid-column: 1 / -1; min-width: 0; font-family: var(--sans); font-size: var(--fs-meta); color: var(--muted);
  overflow-wrap: anywhere; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
/* D342 (Slate design's 068): the row wraps EVERY text child (above), so a child added later inherits it - the detail
   carries the process.unknown paths. The detail shows three lines; .open ("Show full detail") shows the rest. The verdict
   controls sit on two lines: the lead-in, then the three buttons in one group that never wraps (and never shrinks). */
.cond-detail.open { display: block; -webkit-line-clamp: unset; overflow: visible; }
.cond-row > .cond-more { grid-column: 1 / -1; justify-self: start; font-size: var(--fs-meta); }
.cond-row > .pdisp-row { grid-column: 1 / -1; display: flex; flex-direction: column; align-items: flex-start; gap: 6px; margin-top: 5px; }
.pdisp-btns { display: flex; gap: 6px; flex-wrap: nowrap; }
.pdisp-btns > button { flex: none; }
/* D406 (Slate design's 104, 3a): the line that labels every open shadow verdict at once - between the Current Problems
   header and the first row, on the widget's own ground (the mock's tint is not taken: D393, a quiet button never sits on a
   tinted ground). The lead-in in --muted, then the three quiet small buttons in one group that never splits, as a row's
   controls (D342). After a bulk label the lead-in reads the outcome; a partial one in --warn. The confirm's list scrolls. */
.cond-bulk { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; padding: 0 0 11px; border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }
.cond-bulk-lead { color: var(--muted); font-size: var(--fs-meta); }
.cond-bulk-lead.warn { color: var(--warn); }
.cond-bulk-btns { display: flex; gap: 6px; flex-wrap: nowrap; }
.cond-bulk-btns > button { flex: none; }
.cond-bulk-list { max-height: 150px; overflow: auto; margin: 10px 0 0; padding: 8px 10px; list-style: none; border: 1px solid var(--border); border-radius: var(--radius); font-family: var(--mono); font-size: var(--fs-data-sm); overflow-wrap: anywhere; }
/* A button that reads as a link (D342's "Show full detail"): the anchor's colour and hover, a button's semantics. */
button.linkbtn { background: none; border: 0; padding: 0; margin: 0; font: inherit; color: var(--accent); cursor: pointer; text-align: left; }
button.linkbtn:hover { text-decoration: underline; }
/* The Current Problems coverage list (Slate design's 063, D331): under a top border, the "Coverage" heading in the sheet's
   one caps role, then one grid row per sweep - its label in a 104px column. A warning is --warn, label and text (its own
   rule: the app's bare "warn" class has none); the clean notes fold behind one <details> line. */
.cond-cov { border-top: 1px solid var(--border); margin-top: 8px; padding-top: 10px; }
.cond-cov-h { font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.5px; text-transform: uppercase; color: var(--muted); margin-bottom: 6px; }
.cond-cov-row { display: grid; grid-template-columns: 104px minmax(0, 1fr); column-gap: 10px; padding: 2px 0; font-size: var(--fs-meta); color: var(--muted); }
.cond-cov-row > span { min-width: 0; overflow-wrap: anywhere; }
.cond-cov-row.warn { color: var(--warn); }
.cond-cov-lbl { font-weight: 600; }
.cond-cov-fold > summary { cursor: pointer; padding: 2px 0; font-size: var(--fs-meta); color: var(--muted); }
/* Widget body B: labelled bar list. A distribution the operator reads as a shape,
   with the label and the number both present as text — which a canvas cannot do. */
.wbars { display: grid; gap: 7px; }
.wbars .wbar { display: grid; grid-template-columns: 9em 1fr 3.5em; align-items: center; gap: 10px; }
.wbars .wbar > .l { color: var(--muted); font-size: var(--fs-meta); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wbars .wbar > .t { height: 8px; border-radius: 4px; background: var(--panel2); overflow: hidden; }
.wbars .wbar > .t > i { display: block; height: 100%; background: var(--accent); }
.wbars .wbar > .n { font-family: var(--mono); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; text-align: right; }

/* Endpoint groups */
details.grp { margin-bottom: 14px; }
details.grp > summary {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 12px 16px;
  cursor: pointer;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--sans);
  font-size: var(--fs-ui);
  font-weight: 600;
  backdrop-filter: blur(calc(var(--blur) * 0.75));
  transition: border-color .12s;
}
details.grp > summary:hover { border-color: var(--border-strong); }
details.grp[open] > summary { border-radius: var(--radius-lg) var(--radius-lg) 0 0; border-bottom: none; }
details.grp > table { border-radius: 0 0 var(--radius-lg) var(--radius-lg); border-top: none; }
details.grp > table thead tr:first-child th:first-child, details.grp > .dtwrap > .dtscroll > table thead tr:first-child th:first-child,
details.grp > table thead tr:first-child th:last-child,  details.grp > .dtwrap > .dtscroll > table thead tr:first-child th:last-child { border-top-left-radius: 0; border-top-right-radius: 0; }
/* A whole panel folded under a summary (endpoint App Usage): flush to the header like a table. */
details.grp > .policypanel { border-radius: 0 0 var(--radius-lg) var(--radius-lg); border-top: none; margin-bottom: 0; }

/* Searchable + sortable dataset tables (endpoint detail) */
/* A hairline fade on the scrolling edge (6a D58). `local` attaches the gradient to the
   scrolled content, so it sits at the right edge only while there is more to the right
   and slides away at the end. No JS, no new colour: --panel fading into nothing.
   (D56: this box is the scroll container, so a th inside it sticks to IT, not the page.) */
.dtscroll { overflow-x: auto; background:
  linear-gradient(to left, var(--panel) 0 12px, transparent 12px 28px) right center / 28px 100% no-repeat local; }
.dtwrap > .toolbar { margin: 12px 0 10px; }
/* 5b D96: --table-head-top is the page's offset. A th inside its own scroll
   container measures from that box, where the bar is not. */
.dtscroll th { top: 0; }
.dtsearch { min-width: 220px; max-width: 360px; }
/* .pager (Slate phase 9a, D157): below its table, right-aligned - "Rows per page [25] 1–25 of 1,234 ‹ ›". A kit
   primitive composed from the quiet button (button.run.cancel), the kit combo and --muted: no token of its own. The
   range uses tabular figures so it does not jitter between pages, at the body size. */
.pager { margin: 6px 0 4px; }
.pager-row { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
.pager-status { margin-right: auto; }
.pager-size, .pager-range { color: var(--muted); }
.pager-range { font-variant-numeric: tabular-nums; }
.pager .combo input[type=text] { width: 72px; }
.pager button.run.cancel { min-width: 32px; }
.pager-newer { text-align: right; margin: 0 0 4px; }
/* A page change keeps the current rows, dimmed, until the next page lands - no skeleton (6e's is for first paint),
   and the table's height does not collapse. */
table[aria-busy="true"] tbody { opacity: .5; }
th.sortable .arr { font-size: 11px; color: var(--muted); font-weight: 400; letter-spacing: 0; }
/* Active sort column reads ion — it's the one interactive state on the header row. */
th.sortable[data-dir] { color: var(--accent); text-shadow: 0 0 calc(var(--glow) * 0.5) var(--ion-glow); }
th.sortable[data-dir] .arr { color: var(--accent); }
/* Flush the table body to its summary, matching plain .grp tables. */
details.grp > .dtwrap > .dtscroll > table { border-top: none; border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
/* Nested per-stream groups inside "Scheduled Query Results". */
.qrgroups { margin-top: 4px; }
details.grp.qrgroup { margin: 8px 0 0; }
details.grp.qrgroup > summary { padding: 9px 14px; font-weight: 400; text-transform: none; letter-spacing: .3px; }
.okc { color: var(--ok); }
.badc { color: var(--bad); }
a.grpmove, a.eprefresh, a.epdebug { color: var(--muted); font-family: var(--mono); font-size: 11px; }
a.grpmove:hover, a.eprefresh:hover, a.epdebug:hover { color: var(--accent); text-decoration: none; text-shadow: 0 0 calc(var(--glow) * 0.5) var(--ion-glow); }

/* User form + roles */
.uform { display: grid; grid-template-columns: repeat(3, minmax(180px, 300px)); gap: 12px; }
.uform input[type=text], .uform input[type=password] { width: 100%; }
.uform-actions { grid-column: 1 / -1; display: flex; align-items: center; gap: 12px; }
/* D328: a create form's field is one grid cell - its visible label above it, in the kit's label style. The field still
   fills the cell, as grid stretch made it do as a bare grid item (the rule above covers text and password). */
.uform-field > label { display: block; }
.uform-field > select, .uform-field > input[type=number] { width: 100%; }
button.run.cancel { background: none; color: var(--faint); border: 1px solid var(--border-strong); text-shadow: none; }
button.run.cancel:hover { box-shadow: none; color: var(--text); border-color: var(--border-strong); }
/* The kit's small button size (Slate design's 063, D330): the quiet button beside a row's own text - the Current Problems
   verdict buttons, and every button that used to wear the unstyled "small" class. */
button.run.sm { padding: 3px 10px; font-size: var(--fs-data-sm); }
/* D407 (Slate design's 107): the kit's ONE danger button - candidate C, a flat --bad fill with the ground's colour as its
   label (--void: the opaque ground token in every skin; --panel is translucent in the four cosmetic presets and would let
   the red through the label). No gradient. Hover follows button.run's own state with --bad in place of --accent (a glow,
   0px in Slate); focus-visible is the kit's own (phase 2). Only a confirm's OK, for an action logged as a failed event or
   one that cannot be undone - never on a row or a page. Measured AA in all eight skin x theme pairs (driver-d406-danger). */
button.run.danger { background: var(--bad); border-color: var(--bad); color: var(--void); text-shadow: none; }
button.run.danger:hover:not(:disabled) { box-shadow: 0 0 calc(var(--glow) * 0.875) color-mix(in srgb, var(--bad) 45%, transparent); }
td.actions a { margin-right: 10px; color: var(--muted); font-family: var(--mono); font-size: 11px; }
td.actions a:hover { color: var(--accent); text-decoration: none; text-shadow: 0 0 calc(var(--glow) * 0.5) var(--ion-glow); }
/* D313: reasonButton - a control disabled WITH a reason is a real <button>, and must look exactly like the muted text
   it replaced. Every user-agent button default is undone here; the colour stays .muted's. */
.reason-btn {
  appearance: none; -webkit-appearance: none;
  display: inline; background: none; border: 0; border-radius: 0; padding: 0; margin: 0; box-shadow: none;
  font: inherit; letter-spacing: inherit; text-transform: inherit; text-align: inherit; cursor: inherit;
}
@media (max-width: 800px) { .uform { grid-template-columns: 1fr; } }

/* Group rename/delete controls */
a.grpren { color: var(--muted); text-decoration: none; margin-left: auto; padding: 0 6px; }
a.grpren:hover { color: var(--accent); }
a.grpdel { color: var(--muted); text-decoration: none; padding: 0 6px; font-weight: 600; }
a.grpdel:hover { color: var(--bad); }

/* Policies */
.pform { display: flex; flex-direction: column; gap: 12px; max-width: 560px; }
.pform label { display: flex; flex-direction: column; gap: 5px; font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px; }
.pform label.chk { flex-direction: row; align-items: center; gap: 8px; text-transform: none; letter-spacing: 0; font-family: var(--sans); font-size: 14px; font-weight: 400; color: var(--text); }
.pform input[type=text], .pform input[type=number], .pform input[type=password] { background: var(--panel2); border: 1px solid var(--border-strong); border-radius: var(--radius); color: var(--text); padding: 9px 12px; font-family: var(--mono); font-size: 12.5px; transition: border-color .12s, box-shadow .12s; }
.pform input:focus { border-color: var(--accent); box-shadow: 0 0 calc(var(--glow) * 0.75) var(--ion-glow); }
.pform textarea { background: var(--panel2); border: 1px solid var(--border-strong); border-radius: var(--radius); color: var(--text); padding: 9px 12px; font-family: var(--mono); font-size: var(--fs-data); line-height: 1.7; resize: vertical; transition: border-color .12s, box-shadow .12s; }
.pform textarea:focus { border-color: var(--accent); box-shadow: 0 0 calc(var(--glow) * 0.75) var(--ion-glow); }
.pform select { background: var(--panel2); border: 1px solid var(--border-strong); border-radius: var(--radius); color: var(--text); padding: 8px 11px; font-family: var(--mono); font-size: var(--fs-ui); }
.pform fieldset { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px; display: grid; grid-template-columns: 1fr 1fr; gap: 12px; background: var(--panel); backdrop-filter: blur(calc(var(--blur) * 0.75)); }
.pform fieldset legend { color: var(--text); font-family: var(--sans); font-size: var(--fs-head); font-weight: 600; padding: 0 6px; }
.pform-actions { display: flex; align-items: center; gap: 12px; }
.pform .upl { gap: 6px; }
.pform input[type=file] { color: var(--muted); font-size: 13px; background: var(--panel); border: 1px solid var(--border-strong); border-radius: var(--radius); padding: 7px 9px; }
.platrow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.platrow > span.mono { min-width: 110px; }
.platrow input[type=text] { flex: 1; min-width: 220px; }

/* Package "Binaries" section: stack platforms, each on one aligned row. */
.pform fieldset.binaries { display: block; }
.pform fieldset.binaries .binaries-help { margin: 0 0 14px; font-size: 13px; line-height: 1.55; }
.pform fieldset.binaries .binaries-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 6px; }
.pform fieldset.binaries #getinfo:not(:empty) { margin: 8px 0; }
.pform fieldset.binaries .platrow {
  display: grid;
  grid-template-columns: 120px minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  margin-top: 10px;
}
.pform fieldset.binaries .platrow > span.mono { min-width: 0; }
.pform fieldset.binaries .platrow input[type=text] { flex: none; min-width: 0; width: 100%; }
.pform fieldset.binaries .platrow input[type=file] { justify-self: start; }
@media (max-width: 720px) { .pform fieldset.binaries .platrow { grid-template-columns: 1fr; } }
.policybar { padding: 10px 16px; background: var(--panel2); border: 1px solid var(--border); border-top: none; font-family: var(--mono); font-size: 11px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pchip { background: var(--accent-weak); color: var(--accent); border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); border-radius: 20px; padding: 2px 12px; font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; }
.pchip a { color: var(--muted); margin-left: 6px; text-decoration: none; }
.pchip a:hover { color: var(--bad); }
/* Package requirement indicators (file-scan → osquery). Green = requirement met
   for this group; red = a required package is missing there. */
.pchip .reqok { color: var(--ok); margin-left: 5px; cursor: help; }
.pchip .reqbad { color: var(--bad); margin-left: 5px; cursor: help; }
/* A required package can't be removed while its dependent is assigned — a lock
   replaces the ✕ remove control (the server enforces this too). */
.pchip .reqlock { margin-left: 6px; cursor: not-allowed; opacity: 0.75; }
.small { font-size: 11px; }
.policybar a.passign { color: var(--muted); font-size: var(--fs-ui); }
.policybar a.passign:hover { color: var(--accent); text-decoration: none; }
.policypanel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px 20px; margin-bottom: 22px; }
.policypanel h3 { font-family: var(--sans); font-size: var(--fs-head); font-weight: 600; color: var(--text); margin-bottom: 10px; }

/* Field hint balloons */
.hint {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--accent-weak); border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent); font-family: var(--mono); font-size: var(--fs-micro); font-weight: 700; cursor: help;
  position: relative; vertical-align: middle; margin-left: 4px;
}
.hint:hover { border-color: var(--accent); box-shadow: 0 0 calc(var(--glow) * 0.625) var(--ion-glow); }
.hint::after {
  content: attr(data-tip);
  position: absolute; bottom: 140%; left: 50%; transform: translateX(-50%);
  width: max-content; max-width: 260px;
  background: var(--console); color: var(--console-text);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, transparent); border-radius: var(--radius);
  padding: 9px 11px; font-family: var(--sans); font-size: var(--fs-meta); font-weight: 400; line-height: 1.5;
  text-transform: none; letter-spacing: normal; white-space: normal;
  box-shadow: 0 6px 24px var(--shadow), 0 0 calc(var(--glow) * 0.875) var(--ion-weak);
  opacity: 0; visibility: hidden; transition: opacity .12s; z-index: 45;   /* 5b D99: above the bar (40), below .bulkbar (50) */
}
/* D313 (Slate design's 054): a reason may not depend on hover alone - the same rule opens the balloon for a keyboard
   focus. Escape on a focused hint closes it until focus leaves (app.js sets data-tip-closed); a hover still opens it. */
.hint:hover::after, .hint:focus-visible::after { opacity: 1; visibility: visible; }
.hint[data-tip-closed]:not(:hover)::after { opacity: 0; visibility: hidden; }

/* Remote log viewer (endpoint detail → Agent Logs) */
/* My Profile → Appearance: skin picker + custom colour tuners.
   (docs/ui-ion-design-system.md — presets live in app.js SKINS.) */
.skins { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin-bottom: 8px; }
.skin-card {
  cursor: pointer; text-align: left; font-family: var(--mono);
  background: var(--panel2); border: 1px solid var(--border); border-radius: 8px;
  padding: 12px; color: var(--muted);
  transition: border-color .15s, box-shadow .15s, color .15s;
}
.skin-card:hover { border-color: var(--border-strong); color: var(--text); }
.skin-card.active {
  border-color: var(--accent); color: var(--text);
  box-shadow: 0 0 calc(var(--glow) * 0.875) var(--ion-glow), inset 0 0 calc(var(--glow) * 1.375) var(--ion-weak);
}
.skin-card .chips { display: flex; gap: 5px; margin-bottom: 9px; }
.skin-card .chips i { width: 20px; height: 20px; border-radius: 5px; border: 1px solid var(--border-strong); }
.skin-card b { display: block; font-family: var(--sans); font-size: var(--fs-ui); font-weight: 600; }
.skin-card small { font-family: var(--sans); font-size: var(--fs-micro); color: var(--muted); }
.tuner { display: flex; flex-direction: column; gap: 10px; font-family: var(--mono); font-size: 11px; }
.tuner-row { display: grid; grid-template-columns: 90px auto 130px 1fr; align-items: center; gap: 12px; color: var(--muted); }
.tuner-row input[type=color] {
  width: 44px; height: 26px; padding: 1px; cursor: pointer;
  background: var(--panel2); border: 1px solid var(--border-strong); border-radius: 6px;
}
.tuner-row .hexlbl { color: var(--faint); font-family: var(--mono); font-size: var(--fs-data-sm); }
.tuner .btnrow { display: flex; align-items: center; gap: 12px; margin-top: 4px; }
@media (max-width: 640px) { .tuner-row { grid-template-columns: 80px auto 1fr; } .tuner-row .muted { display: none; } }

/* Remote log viewer — a console: dark ground in every skin. */
.logbox {
  max-height: 420px;
  overflow: auto;
  margin: 0;
  padding: 12px 14px;
  background: var(--console);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  font-family: var(--mono);
  font-size: var(--fs-data-sm);
  line-height: 1.7;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--console-dim);
}
/* The console's complete ink set is --console-text / -dim / -faint, constants in
   both Slate schemes for the reason the sidebar group is: the surface does not
   flip, so its ink must not either (delta 1, D10). Never a content token here. */
.logbox.muted { color: var(--console-faint); }

/* Live Query results: endpoint name links to its detail page */
.qhostlink { color: inherit; text-decoration: none; }
.qhostlink:hover { color: var(--accent); text-decoration: underline; }

/* Live Query targets — group→endpoints tree */
.qtargets { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--panel); margin: 10px 0; overflow: hidden; }
.qt-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 8px 12px; border-bottom: 1px solid var(--border-strong); background: var(--panel2); font-family: var(--mono); font-size: var(--fs-data-sm); flex-wrap: wrap; }
.qt-alllabel { cursor: pointer; display: inline-flex; align-items: center; gap: 6px; }
.qt-tree { max-height: 320px; overflow: auto; padding: 4px 0; }
.qt-group { border-bottom: 1px solid var(--border); }
.qt-group:last-child { border-bottom: none; }
.qt-group > summary { list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 7px 12px; }
.qt-group > summary::-webkit-details-marker { display: none; }
.qt-group > summary::before { content: "▸"; color: var(--accent); font-size: var(--fs-micro); margin-right: 2px; transition: transform .12s; }
.qt-group[open] > summary::before { transform: rotate(90deg); }
.qt-grplabel { cursor: pointer; display: inline-flex; align-items: center; gap: 6px; flex: 1; font-family: var(--mono); font-size: var(--fs-data-sm); }
.qt-members { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 4px 12px 12px 30px; font-family: var(--mono); font-size: 11px; }
.qt-ep { cursor: pointer; display: inline-flex; align-items: center; gap: 5px; color: var(--muted); }
.qt-ep:hover { color: var(--text); }

/* ---- facet-detail modal (top-box / security summaries -> full facet table) */
.modal-overlay { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center;
  justify-content: center; padding: 24px;
  /* A scrim is always DARK: it used to be --void at 62%, which under Slate Light
     bleached the page — sidebar included — behind a dialog. Each cosmetic preset
     sets --scrim to its own --void at that same 0.62, so it is pixel-identical
     FOR A PRESET'S DEFAULT GROUND — and only that (delta 2, D32). The scrim is
     chrome: a user's custom --void retints the content ground, and the dialog
     that covers it stays the preset's. */
  background: var(--scrim); backdrop-filter: blur(calc(var(--blur) * 0.375)); }
/* Two shadows: the tight second one is what lifts the dialog off a WHITE page at
   --shadow's 10% alpha. The geometry is the component's; --shadow is the skin's. */
.modal { display: flex; flex-direction: column; overflow: hidden; min-width: min(440px, 92vw);
  max-width: min(1100px, 94vw); max-height: 84vh;
  background: var(--panel); border: 1px solid var(--line-strong); border-radius: 10px;
  box-shadow: 0 14px 48px var(--shadow), 0 2px 8px var(--shadow); }
.modal-head { display: flex; align-items: center; gap: 8px; padding: 12px 16px;
  border-bottom: 1px solid var(--line); }
.modal-head h3 { margin: 0; }
.modal-close { margin-left: auto; padding: 2px 9px; cursor: pointer; border-radius: 5px;
  background: none; border: 1px solid var(--line); color: var(--muted);
  font-family: var(--mono); font-size: var(--fs-ui); }
.modal-close:hover { color: var(--ion); border-color: var(--ion); }
.modal-body { overflow: auto; padding: 10px 16px 16px; }
/* The policy dialog (D319, Slate design's 057). 960 px wide and the viewport's height less 80 px - FIXED at that cap, so
   choosing a section never resizes it - with a 220 px section rail, one section showing at a time, and a footer. Under
   900 px it fills the viewport and the rail becomes a select above the section. 057's --qr-high and --qr-crit are the
   kit's --sev-high and --sev-crit (the mock's footer amber is --sev-high's light value, #b45309). */
.modal-overlay.pdlg-overlay { padding: 40px 24px; }
.modal.pdlg { width: 960px; max-width: 100%; height: 100%; max-height: none; }
.pdlg-head { align-items: flex-start; }
.pdlg-titles { flex: 1; min-width: 0; }
.pdlg-sub { margin-top: 2px; font-size: var(--fs-meta); }
.pdlg-main { flex: 1; display: flex; min-height: 0; }
.pdlg-rail { flex: 0 0 220px; display: flex; flex-direction: column; gap: 2px; padding: 10px 8px; overflow-y: auto;
  border-right: 1px solid var(--line); background: var(--panel2); }
.pdlg-tab { display: flex; align-items: center; gap: 8px; padding: 7px 10px; border: none; border-radius: var(--radius);
  background: none; color: var(--muted); font-family: var(--sans); font-size: var(--fs-ui); text-align: left; cursor: pointer; }
.pdlg-tab:hover { color: var(--text); }
.pdlg-tab[aria-selected="true"] { background: var(--accent-weak); color: var(--accent); font-weight: 600; }
.pdlg-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.pdlg-dot { flex: none; width: 7px; height: 7px; margin-left: auto; border-radius: 50%; }
.pdlg-tab.is-dirty .pdlg-dot { background: var(--sev-high); }
.pdlg-tab.is-err .pdlg-dot { background: var(--sev-crit); } /* an error outranks a change */
.pdlg-railsel { display: none; }
.pdlg-panes { flex: 1; min-width: 0; overflow-y: auto; padding: 18px 24px; }
.psec[hidden] { display: none; }
.psec-title { margin: 0; font-size: var(--fs-head); }
.pdlg-foot { display: flex; flex-direction: column; gap: 6px; padding: 12px 20px; border-top: 1px solid var(--line); }
.pdlg-err:empty { display: none; }
.pdlg-note { color: var(--sev-high); font-size: var(--fs-meta); }
.pdlg-note:empty { display: none; }
/* The read-only view (D299): a row's remove link reads as the muted text it cannot be. */
.pdlg.is-ro .sq_del, .pdlg.is-ro .ac_del { color: var(--muted); pointer-events: none; text-decoration: none; }
.pdlg-bar { display: flex; align-items: center; gap: 10px; }
.pdlg-dirty { color: var(--sev-high); font-size: var(--fs-meta); }
.pdlg-gap { flex: 1; }
@media (max-width: 899px) {
  .modal-overlay.pdlg-overlay { padding: 0; }
  .modal.pdlg { width: 100%; border-radius: 0; }
  .pdlg-main { flex-direction: column; }
  .pdlg-rail { display: none; }
  .pdlg-railsel { display: flex; flex-direction: column; gap: 5px; padding: 12px 16px 0; font-family: var(--sans);
    font-size: var(--fs-micro); font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
  .pdlg-railsel select { background: var(--panel2); border: 1px solid var(--border-strong); border-radius: var(--radius);
    color: var(--text); padding: 8px 11px; font-family: var(--sans); font-size: var(--fs-ui); text-transform: none; letter-spacing: 0; }
  .pdlg-panes { padding: 12px 16px; }
}
/* Row-detail flyout (phase 6d). Subordinate detail for the row you are looking
   at — the list stays on screen behind it. One at a time; never stacked.
   z 70/71 (design's 017): above the bar (40), the bulk bar (50) and the drawer (60, which cannot be open at the
   same time), below the combo (150), the kit modal (200) and the palette (210) - and the palette does not open
   over it (5b D140). The panel is --panel composited onto --bg, 6a D62 and 5b D100's rule for a surface that
   content sits beneath: a preset's translucent --panel would show the list through the detail. No shadow. */
.flyout-scrim { position: fixed; inset: 0; z-index: 70; background: var(--scrim); }
.flyout {
  position: fixed; inset: 0 0 0 auto; z-index: 71;
  width: min(520px, 100%);   /* 6d D143: a fixed element's 100% is the viewport WITHOUT the scrollbar; 100vw
                                counted it and sat the panel 15px off screen at narrow widths */
  display: flex; flex-direction: column;
  background-color: var(--bg); background-image: linear-gradient(var(--panel), var(--panel));
  border-left: 1px solid var(--border-strong);
}
.flyout-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
  background: var(--panel2);
}
.flyout-title { font-family: var(--mono); font-size: var(--fs-head); font-weight: 600; }   /* 6d D85's --fs-h3 does not exist (017) */
.flyout-body { flex: 1 1 auto; overflow-y: auto; padding: 16px 18px; min-height: 0; }

/* Tabs (phase 6e). One row of controls, one hairline, no capsules. The active
   tab is the only ion in the row (law 1) and carries the 2px underline. */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin: 0 0 18px; }
.tab {
  padding: 9px 14px; background: none; border: none; border-bottom: 2px solid transparent;
  color: var(--muted); font-family: var(--mono); font-size: var(--fs-ui);
  letter-spacing: .04em; cursor: pointer;
}
.tab:hover { color: var(--text); }
.tab[aria-selected="true"] { color: var(--accent); border-bottom-color: var(--accent); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* The row is not sticky (6e D97): the bar and the table heads already are. A
   tab panel is focusable (tabindex 0) so a keyboard user can scroll it; it takes
   the global focus ring.
   At phone width the seven tabs need 615px against a 337px row, and the row
   pushed the WHOLE PAGE 264px sideways (measured at 380px). So the row scrolls
   within itself, and a tab keeps its width rather than wrapping its label. A
   classic scrollbar there sat between the underline and the hairline at full
   weight, arrows and all; thin keeps the affordance without the weight. */
.tabs { overflow-x: auto; scrollbar-width: thin; }
.tab { flex: none; white-space: nowrap; }
/* A tab in flight renders its panels' own shape, never a spinner and never an
   empty panel (6e D95). The rows are real table rows, so they keep 6a's row
   height; .skel is the band inside a cell, kept within the cell's line box. The
   global prefers-reduced-motion rule stops the pulse. */
.skel {
  display: inline-block; width: 60%; height: .8em; vertical-align: middle;
  border-radius: var(--radius); background: var(--panel2); animation: skel 1.4s ease-in-out infinite;
}
@keyframes skel { 50% { opacity: .45; } }
.epskel { margin: 0 0 22px; }
/* A payload that did not load says so in its panel's place, with its Retry
   beside the sentence (6e D152). */
.epfail { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 12px; margin: 0 0 14px; }
.epfail > p { margin: 0; }
/* Full-detail crypto-asset modal: the classification reasons list. */
.pqc-reasons { margin: 0; padding-left: 18px; }
.pqc-reasons li { margin: 2px 0; line-height: 1.4; color: var(--text); }
.modal-body h4 { margin: 14px 0 6px; font-size: 13px; color: var(--muted); }
/* Summary values that open a facet modal: subtle affordance, ion on hover. */
a.facet-pop { color: inherit; text-decoration: none; cursor: pointer;
  border-bottom: 1px dotted color-mix(in srgb, var(--ion) 55%, transparent); }
a.facet-pop:hover { color: var(--ion); border-bottom-color: var(--ion); }

/* ---- AI posture assessment panel — the FIRST plasma consumer (law 3:
   plasma = the AI speaking, nowhere else). */
.aipanel { background: var(--panel); border: 1px solid color-mix(in srgb, var(--plasma) 35%, transparent);
  border-radius: var(--radius-lg); padding: 16px 20px; margin-bottom: 22px;
  box-shadow: 0 0 calc(var(--glow) * 1.125) var(--plasma-weak); }
.aipanel h3 { font-family: var(--sans); font-size: var(--fs-head); font-weight: 600; color: var(--plasma);
  margin-bottom: 10px; }
.ai-grades { display: flex; gap: 12px; flex-wrap: wrap; margin: 10px 0 12px; }
.ai-grade { flex: 1 1 120px; min-width: 120px; padding: 10px 12px; border-radius: 8px;
  background: var(--panel2); border: 1px solid var(--line); text-align: center; }
.ai-grade .g { font-family: var(--mono); font-size: 26px; font-weight: 700; }
.ai-grade .g.gA, .ai-grade .g.gB { color: var(--ok); }
.ai-grade .g.gC { color: var(--warn); }
.ai-grade .g.gD, .ai-grade .g.gF { color: var(--bad); }
.ai-grade .lbl { font-family: var(--sans); font-size: var(--fs-micro); font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.5px; color: var(--muted); margin-top: 2px; }
.ai-grade .score { font-size: 11px; color: var(--faint); font-variant-numeric: tabular-nums; }
.ai-summary { border-left: 2px solid var(--plasma); padding: 6px 12px; margin: 10px 0;
  color: var(--text); background: var(--plasma-weak); border-radius: 0 6px 6px 0; }
.ai-finding { margin: 4px 0; }
.ai-finding .ev { color: var(--muted); font-family: var(--mono); font-size: var(--fs-data-sm); }
.ai-meta { color: var(--faint); font-size: 11px; font-family: var(--mono); margin-top: 8px; }

/* Plasma widget variant — the AI layer on the dashboard (law 3). */
.widget.ai { border-color: color-mix(in srgb, var(--plasma) 35%, transparent);
  box-shadow: 0 0 calc(var(--glow) * 1.125) var(--plasma-weak); }
.widget.ai h3 { color: var(--plasma); }
.widget.ai .ai-grade { min-width: 50px; flex-basis: 50px; padding: 8px 4px; }
.widget.ai .ai-grade .g { font-size: 20px; }
.widget.ai .vrow a { color: inherit; }

/* ============================================================================
   Mobile pass (2026-07-18, user directive: the dashboard must be usable —
   starting with login — from a phone). The existing 800px breakpoint already
   turns the sidebar into a wrapping top bar; this block fixes what actually
   breaks at handset widths. Tokens only, no per-page styling.
   ========================================================================== */
/* A fixed-width input must never overflow its container (340px > a 375px
   viewport minus padding). Applies at every width — harmless on desktop. */
input[type=text] { max-width: 100%; }

@media (max-width: 800px) {
  main { --main-pad-y: 14px; --main-pad-x: 14px; }   /* 5b D93: the padding, through its variables */
  h1 { font-size: var(--fs-section); }

  /* Bare tables (Endpoints, Users, Events, Audits…) pan horizontally instead
     of crushing the layout; dataTables already scroll via .dtscroll. */
  table { display: block; overflow-x: auto; }
  th { top: 0; }   /* 5b D96: the table is its own scroll container here - the bar is not in it */
  .dtscroll table { display: table; } /* already wrapped — keep real table layout */
  th, td { white-space: nowrap; }

  /* iOS Safari zooms any focused control under 16px — hold inputs at 16px so
     tapping a field doesn't lurch the viewport. */
  input[type=text], input[type=password], select, textarea { font-size: 16px; }

  /* Counter tiles compress to fit more per row. */
  .cards { gap: 8px; }
  .card { padding: 10px 14px; min-width: 104px; }
  .card .num { font-size: 20px; }

  /* Full-width pickers/search on touch. */
  .combo, .combo input[type=text] { width: 100%; }
  .toolbar { gap: 10px; }

  /* The floating bulk bar spans the bottom instead of center-floating. */
  .bulkbar { left: 8px; right: 8px; transform: none; flex-wrap: wrap; justify-content: center; bottom: 10px; }

  /* Modals fill the width; the 84vh cap + scrolling body already hold. */
  .modal { min-width: 0; width: 94vw; }

  .info-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
}

/* ---- Kit toggle switch ----------------------------------------------------
   A THEMED on/off control: native checkboxes render with OS accent colors and
   ignore the skin (the same reason selects got the combo box). Markup:
     <label class="switch"><input type="checkbox" id="..."><span class="knob"></span> Label</label>
   The input stays in the DOM (hidden) so existing .checked logic is untouched. */
.switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .knob {
  flex: 0 0 auto; width: 36px; height: 20px; border-radius: 10px;
  background: var(--panel2); border: 1px solid var(--line-strong);
  position: relative; transition: background .15s, border-color .15s;
}
.switch .knob::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px;
  border-radius: 50%; background: var(--muted);
  transition: left .15s, background .15s;
}
.switch input:checked + .knob { background: var(--accent-weak); border-color: var(--accent); }
.switch input:checked + .knob::after { left: 18px; background: var(--accent); }
.switch input:focus-visible + .knob { outline: 2px solid var(--accent); outline-offset: 2px; }
.switch:hover .knob { border-color: var(--accent); }

/* Live scan progress in the Probe Deployment console. Tokens only — a thin bar
   under the status badge so a long-running network scan reads as "working"
   rather than "possibly wedged". */
.pdbar {
  height: 3px;
  margin-top: 4px;
  max-width: 260px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
}
.pdbar > span {
  display: block;
  height: 100%;
  background: var(--accent);
  transition: width .4s ease;
}
/* Live output of an action task that is running right now. Tokens only. Bounded
   height with its own scroll and anchored to the BOTTOM, because the newest
   lines are the point — a box that grows with the output would push the rest of
   the table off screen during a long script. */
.tasklive { margin-top: 6px; max-width: 640px; }
.tasklivebox {
  margin: 4px 0 0;
  padding: 8px 10px;
  max-height: 180px;
  overflow: auto;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-family: var(--mono);
  font-size: var(--fs-data);
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text);
}
/* Indeterminate variant: a DLP walk has no knowable total (incremental frontier),
   so a moving stripe honestly signals "actively scanning" without a false %. */
.pdbar.indet {
  overflow: hidden;
}
.pdbar.indet > span {
  width: 40% !important;
  transition: none;
  border-radius: 2px;
  animation: pdbar-indet 1.2s ease-in-out infinite;
}
@keyframes pdbar-indet {
  0% { margin-left: -40%; }
  100% { margin-left: 100%; }
}

/* Act-as tenant strip (multi-tenancy slice 3c). Quiet at home, loud away —
   the operator must never have to remember whose fleet is on screen. */
/* It MOUNTS IN THE TOP BAR (renderTenantStrip appends it to #topbarScope), whose
   ground is the dark shell's --side (5b D91), so it keeps --side-* ink in every Slate
   scheme — including its links (the global `a` rule reads --accent, navy on
   near-black under Slate Light) and its .muted label. Its old outer margin was
   spacing between it and its sidebar neighbours and went with them (5b, D63 as
   amended); everything else here is unchanged. */
.tenantstrip {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 10px;
  color: var(--side-text2); background: var(--side2);
  border: 1px solid var(--side-line); border-radius: 6px;
  font-size: var(--fs-meta);
}
.tenantstrip a { font-size: var(--fs-ui); color: var(--side-accent); }
.tenantstrip .muted { color: var(--side-text2); }
.tenantstrip.away {
  color: var(--side-crit); border-color: var(--side-crit); background: var(--side-crit-bg);
}
/* Its "acting as" chip is a status badge, and a light-scheme status badge is
   unreadable here. Scoped to this ONE badge on purpose.
   THE STRIP OWNS THE TINT; THE CHIP OWNS THE BORDER (delta 2, D23). This chip sits
   inside the away strip, and --side-crit-bg is a wash: carrying it here too
   composited it TWICE — measured 3.80:1 in both Slate schemes. Transparent, it
   reads 4.90 on the strip's single layer, and still reads as a chip. */
.tenantstrip.away .badge.bad { color: var(--side-crit); background: transparent; border-color: var(--side-crit); }   /* 5b D91: not #sidebar-scoped any more. 6f-2 D286: .badge.bad is what badge("error") writes (D293 removed .badge.error: driver-6f-2's G2 proves no literal is left under the strip) */

/* Application Control tile filter: rows hidden by a CLASS, never style.display —
   the dataTable search box owns style.display, and two mechanisms on one
   property fight (the search would silently un-hide filtered rows). */
tr.acfhide { display: none !important; }

/* ---- Live Query question bar (docs/nl-fleet-query-scope.md) ---- */
.nlbar { border: 1px solid var(--border); border-radius: var(--radius-lg); background: var(--panel); padding: 10px 12px; margin: 10px 0; }
.nl-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.nl-input { position: relative; flex: 1 1 420px; }
.nlq { width: 100%; box-sizing: border-box; padding: 9px 12px; font-size: 14px; font-family: var(--mono); }
.nl-complete { max-height: 260px; overflow-y: auto; }
.nl-kind { font-size: 11px; margin-right: 6px; min-width: 62px; display: inline-block; }
.nl-model { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-ui); }
.nl-hint { font-size: var(--fs-meta); margin-top: 6px; }
.nl-hint code { font-family: var(--mono); }
.nl-wait { margin-top: 8px; font-size: var(--fs-ui); color: var(--accent); }
.nl-box { margin-top: 10px; padding: 8px 10px; border-radius: var(--radius-lg); border: 1px solid var(--border); font-size: 13px; }
.nl-box.nl-ok { border-color: var(--accent); }
.nl-box.nl-rejected { border-color: var(--bad); }
.nl-line { margin: 4px 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.nl-sql { white-space: pre-wrap; word-break: break-word; }
.nl-sql-rej { opacity: .6; text-decoration: line-through; white-space: pre-wrap; }
.nl-chip { display: inline-block; padding: 2px 8px; border: 1px solid var(--border); border-radius: 999px; font-size: var(--fs-micro); margin: 2px 4px 2px 0; }
.nl-warn { margin: 4px 0 4px 18px; color: var(--warn); font-size: var(--fs-meta); }
.nl-skipped { font-size: var(--fs-meta); }
.nl-cat { font-size: 11px; margin-left: auto; }
.qmeta { margin: 6px 0 10px; padding: 6px 10px; border-left: 3px solid var(--accent); font-size: 13px; }
.qmeta-sql code { white-space: pre-wrap; word-break: break-word; }
.nl-hist-q { font-family: inherit; color: var(--accent); font-size: var(--fs-ui); }
/* slice 2: the server tier, the answer table, the fleet refresh */
.nl-tier { text-transform: none; }
.nl-surface { font-size: 11px; opacity: .8; word-break: break-word; }
.nl-preds { margin: 4px 0 4px 18px; font-size: var(--fs-meta); }
.nl-preds li { margin: 2px 0; }
.nl-said { font-family: var(--mono); }
.nl-fresh { font-size: var(--fs-meta); display: block; line-height: 1.6; }
.nl-answer-box .qmeta { margin-bottom: 6px; }
.nl-answer-box .dtwrap { margin-top: 6px; }
.nl-owner { margin-left: auto; font-size: var(--fs-ui); }
.nl-notjudged { color: var(--warn); }
.nl-refresh-btn { margin-left: 8px; }
.nl-refresh-status { margin-top: 6px; font-size: var(--fs-ui); }
.nl-refresh-row { margin: 2px 0 2px 12px; }
.nl-floor { text-transform: none; }
.nl-action-text { font-size: 13px; }
.nl-defaults { font-size: var(--fs-meta); }
.nl-missing { font-size: var(--fs-meta); }
.nl-missing-field { border-color: var(--warn); }
label.nl-missing, label.nl-changed { color: var(--warn); }
.nl-before { font-size: var(--fs-meta); margin: 6px 0 10px; padding: 6px 8px; border: 1px dashed var(--border); border-radius: var(--radius-lg); }
.nl-result .nl-rule { font-size: 13px; }
/* slice 3: task verbs, the model's suggested sentence, rule-change undo */
.nl-durability, .nl-signed { text-transform: none; }
.nl-task-verb, .nl-task-spec, .nl-task-2phase { font-size: var(--fs-meta); }
.nl-task-eligible li { color: inherit; }
.nl-task-skipped li, .nl-task-unknown li { opacity: .75; }
.nl-task-hits li, .nl-task-confirm-list li { margin: 3px 0; }
.nl-box.nl-suggest { border-color: var(--warn); border-style: dashed; }
.nl-suggest-sentence { white-space: pre-wrap; word-break: break-word; }
.nl-undo-slot { font-size: var(--fs-meta); }
.nl-undo-window { font-size: 11px; opacity: .8; }

/* The scheduled-scan console's three tiers, side by side where there is room
   and stacked on a narrow screen. Its own class rather than a borrowed grid:
   the first version used a `grid3` that this stylesheet never defined, so the
   tiers rendered with no layout at all. */
.cvs-tiers { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.cvs-tiers > div { flex: 1 1 300px; min-width: 280px; }

/* Weekday checkboxes on the ClamAV scan schedule (Antivirus page). Inline-flex
   so seven days wrap sanely inside a tier column rather than stacking. */
.cvs-days { display: inline-flex; flex-wrap: wrap; gap: .5rem; margin-left: .4rem; }
.cvs-days label { display: inline-flex; align-items: center; gap: .2rem; margin: 0; }

/* Phase 10a (Slate design's brief phase-10-remediation.md, amended by 088): the Remediation page. Kit only (D360): the step row is
   the kit's .tabs, the working-set bar a strip on the accent's weak tint (the ground .hint already stands on), the panel a .card.
   These rules lay the three out; none adds a colour of its own. */
.rm-head { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 6px 24px; margin-bottom: 18px; }
.rm-head h1 { margin-bottom: 4px; }
.rm-head .rm-line, .rm-head .warn { margin: 0; }
.rm-browse { display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: baseline; padding-top: 6px; color: var(--muted); font-size: var(--fs-ui); }
.rm-steps .tab { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: flex-start; gap: 3px; text-align: left; white-space: normal; }
.rm-tabtitle { overflow-wrap: anywhere; }
.rm-tabsub { color: var(--muted); font-family: var(--sans); font-size: var(--fs-meta); letter-spacing: 0; overflow-wrap: anywhere; }
.rm-set { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; margin: 0 0 14px; padding: 9px 14px; border-radius: var(--radius); background: var(--accent-weak); font-size: var(--fs-ui); }
.rm-set-label { color: var(--muted); font-family: var(--mono); font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: .04em; }
.rm-set-text { overflow-wrap: anywhere; }
.rm-set-note { margin-left: auto; color: var(--muted); }
.rm-panel { padding: 18px 22px; }
.rm-stephead h2 { margin: 0 0 4px; }
.rm-stephead p { margin: 0 0 14px; }
/* 10a step 1: the step head with the view toggle at its right, the band groups, a greyed act-now row (no fix released), the pills
   and the footer. The pills are the kit's small run buttons: filled when the pair is in the working set, quiet when it is not. */
.rm-stephead-row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 8px 16px; }
.rm-group { margin: 0 0 16px; }
.rm-grouphead, .rm-group > summary { margin: 0 0 6px; }
.rm-grouplabel { font-weight: 600; }
.rm-off td { color: var(--muted); }
.rm-pktable td, .rm-pills { overflow-wrap: anywhere; }
.rm-pills { display: inline-flex; flex-wrap: wrap; gap: 6px; }
.rm-foot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
/* 10a step 2: the controls row, the Select all line, the ring stage headers, the working set's packages marked in a pending list. */
.rm-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 24px; margin: 0 0 12px; }
.rm-control { display: inline-flex; align-items: center; gap: 8px; }
.rm-clabel { color: var(--muted); font-size: var(--fs-ui); }
.rm-rbwarn { margin: 0 0 12px; }
/* D397: the When control's picker takes the kit's input look (the global rule covers text, number and password only). */
.rm-control input[type=datetime-local] { background: var(--panel2); border: 1px solid var(--border-strong); border-radius: var(--radius);
  color: var(--text); padding: 6px 10px; font-size: 13px; transition: border-color .12s, box-shadow .12s; }
.rm-control input[type=datetime-local]:focus { border-color: var(--accent); box-shadow: 0 0 calc(var(--glow) * 0.75) var(--ion-glow); }
.rm-rule { margin: -4px 0 12px; }
/* D398: a held stage's queue box and its intents' state, after the gate's sentence; a fresh page's queued line */
.rm-queue { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-ui); }
.rm-queuestate { font-size: var(--fs-ui); }
.rm-openq { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; margin: 0 0 12px; }
.rm-openq p { margin: 0; }
.rm-openq-stage { display: inline-flex; align-items: center; gap: 6px; }
.rm-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; margin: 0 0 10px; }
.rm-selall { display: inline-flex; align-items: center; gap: 6px; }
.rm-stage { margin: 0 0 14px; }
.rm-stagehead { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin: 0 0 6px; }
.rm-mine { font-weight: 600; }
.rm-also summary { cursor: pointer; }
.rm-btns { display: inline-flex; flex-wrap: wrap; gap: 8px; }
.rm-s2table td { overflow-wrap: anywhere; vertical-align: top; }
/* 10a step 3: the three totals (the kit's stat tiles) and the result rows. */
.rm-totals { margin-bottom: 12px; }
.rm-s3table td { overflow-wrap: anywhere; vertical-align: top; }
