/* leakwatch — SOC dark theme. CSP: no inline styles anywhere. */

/* Absolute hide. The browser's own [hidden]{display:none} is a UA rule, so
   any author display declaration (display:grid/flex below) silently
   overrides it — the login overlay then refused to disappear after a
   SUCCESSFUL login and the shell refused to appear. This one line fixes
   the entire dashboard gate. */
[hidden] { display: none !important; }

/* =====================================================================
   UIUX_SPEC §3 — Visual system. ONE source of truth for palette,
   type, spacing, radius, elevation, motion. Everything below consumes
   these tokens; no magic paddings/radii/shadows.
   ===================================================================== */
:root {
  /* round-16: native widgets (selects, scrollbars, date inputs) render in
     the dark palette too — no white select popups in a dark theme. */
  color-scheme: dark;

  /* Surfaces (§3.1) */
  --bg-canvas:    #0b0e14;  /* page */
  --bg-panel:     #121722;  /* top bar, sidebar, sheets */
  --bg-card:      #161c29;  /* cards, table, views */
  --bg-elevated:  #1c2333;  /* hover, menu, popups */
  --bg-overlay:   rgba(4, 8, 15, 0.72);      /* scrim */
  --line-hairline:#1e2530;  /* borders, dividers */
  --line-strong:  #2a3344;  /* input borders, focus-adjacent */

  /* Text (§3.1 — all pairs computed AA/AAA in the spec table) */
  --text-primary:   #e6ebf2;
  --text-secondary: #9aa5b8;
  --text-disabled:  #5b6478;  /* decorative/disabled only, never body text */

  /* Accent (indigo) */
  --accent:#5b7cfa; --accent-text:#7aa2f7; --accent-hover:#7d95fb;
  --accent-weak:rgba(91,124,250,.14); --on-accent:#0b1220;

  /* Severity ramp (token NAMES pinned by consumers; values revalued) */
  --sev-critical:#ff7b72; --sev-high:#f5a95c; --sev-medium:#e8c55b;
  --sev-low:#8b93a7;
  --critical:var(--sev-critical); --high:var(--sev-high);
  --medium:var(--sev-medium);    --low:var(--sev-low);
  --state-ok:#6bd48c; --state-warn:var(--sev-high); --state-err:var(--sev-critical);
  --wallet:#f28bb8;
  --focus-ring:#8ab4f8;

  /* Typography (§3.3) */
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --fs-11: 11px; --fs-12: 12px; --fs-13: 13px; --fs-14: 14px;
  --fs-16: 16px; --fs-20: 20px; --fs-24: 24px; --fs-32: 32px;

  /* Spacing / radius / elevation / motion (§3.4) */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:24px;
  --sp-6:32px; --sp-8:48px;
  --radius-sm:6px; --radius-md:10px; --radius-lg:16px; --radius-full:999px;
  --elev-0: none;
  --elev-1: 0 1px 2px rgba(0,0,0,.4);
  --elev-2: 0 4px 12px rgba(0,0,0,.45);
  --elev-3: 0 12px 32px rgba(0,0,0,.55);
  --motion-fast:120ms; --motion-slow:200ms;
  --ease:cubic-bezier(.2,0,0,1);

  /* Layout constants (rule 10: sticky offsets use this, no calc magic) */
  --topbar-h:56px; --rail-w:232px; --rail-w-collapsed:64px;

  /* z-index ladder (UIUX_SPEC §2.1: scrim 60, sheet 70, toast 80,
     login 90) */
  --z-scrim:60; --z-sheet:70; --z-toast:80; --z-login:90;

  /* Deprecated aliases (UIUX_SPEC rule 24). Kept so any missed consumer
     still renders; removed only after the pin suite passes twice on the
     new names. */
  --bg: var(--bg-canvas);
  --panel: var(--bg-panel);
  --line: var(--line-hairline);
  --text: var(--text-primary);
  --dim: var(--text-secondary);
  --ok: var(--state-ok);
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; background: var(--bg-canvas); color: var(--text-primary);
  font: var(--fs-14)/1.45 var(--font-ui); }
h1, h2, h3, h4 { line-height: 1.2; margin: 0; }

kbd, .mono, code { font-family: var(--font-mono); }

/* Screen-reader-only content (visually hidden, announced). */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
  border: 0; }

/* ---------- login (migration rule 1) ---------- */
.login { position: fixed; inset: 0; display: grid; place-items: center;
  background: var(--bg-canvas); z-index: var(--z-login); }
.login-card { display: grid; gap: var(--sp-3); padding: var(--sp-8);
  min-width: 320px; background: var(--bg-card);
  border: 1px solid var(--line-hairline); border-radius: var(--radius-lg);
  box-shadow: var(--elev-2); }
.login-logo { font-weight: 700; letter-spacing: .06em; color: var(--accent-text); }
.login-card label { color: var(--text-secondary); font-size: var(--fs-12);
  text-transform: uppercase; letter-spacing: .08em; }
.login-card input { padding: var(--sp-2) var(--sp-3); background: var(--bg-canvas);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  color: var(--text-primary); font-size: var(--fs-14); }
.login-card button { padding: var(--sp-2); background: var(--accent);
  color: var(--on-accent); border: 0; border-radius: var(--radius-md);
  font-weight: 600; font-size: var(--fs-14); height: 40px; cursor: pointer;
  transition: background var(--motion-fast) var(--ease); }
.login-card button:hover { background: var(--accent-hover); }
.login-error { color: var(--sev-critical); font-size: var(--fs-12); margin: 0; }

/* ---------- app shell (UIUX_SPEC §2.1) ---------- */
.app-shell { display: flex; align-items: flex-start; min-height: 100dvh; }
.shell-content { flex: 1 1 auto; min-width: 0; }

/* Skip link: first focusable node, visible only when focused. */
.skip-link { position: absolute; left: var(--sp-3); top: -64px;
  background: var(--accent); color: var(--on-accent);
  padding: var(--sp-2) var(--sp-4); border-radius: var(--radius-md);
  z-index: calc(var(--z-login) + 1); transition: top var(--motion-fast) var(--ease); }
.skip-link:focus { top: var(--sp-3); }

/* ---------- sidebar (rule 2: .brand moved here) ---------- */
#sidenav { flex: 0 0 var(--rail-w); width: var(--rail-w);
  position: sticky; top: 0; height: 100dvh;
  display: flex; flex-direction: column; gap: var(--sp-1);
  padding: var(--sp-4) var(--sp-2);
  background: var(--bg-panel); border-right: 1px solid var(--line-hairline);
  z-index: calc(var(--z-scrim) - 1); }
.brand { font-weight: 700; letter-spacing: .06em; color: var(--accent-text);
  padding: var(--sp-2) var(--sp-3) var(--sp-4);
  font-size: var(--fs-16); }
.nav-item { display: flex; align-items: center; gap: var(--sp-3);
  min-height: 44px; padding: 0 var(--sp-3);
  background: none; border: 0; border-radius: var(--radius-md);
  color: var(--text-secondary); font: var(--fs-14)/1 var(--font-ui);
  cursor: pointer; text-align: left;
  transition: background var(--motion-fast) var(--ease),
              color var(--motion-fast) var(--ease); }
.nav-item:hover { background: var(--bg-elevated); color: var(--text-primary); }
.nav-item[aria-current="page"] { background: var(--accent-weak);
  color: var(--accent-text); }
.nav-item .nav-icon { width: 20px; flex: 0 0 20px; text-align: center;
  font-size: var(--fs-16); }
.rail-collapse { margin-top: auto; color: var(--text-secondary); }
.rail-collapse .rail-collapse-label-collapsed { display: none; }

/* Collapsed rail (768–1279px or remembered state, UIUX_SPEC §5):
   64px icon rail; labels hidden; the button's title/aria-label carries
   the name. */
body.rail-collapsed #sidenav { flex-basis: var(--rail-w-collapsed);
  width: var(--rail-w-collapsed); }
body.rail-collapsed .brand { padding-left: var(--sp-2); }
body.rail-collapsed .brand-label,
body.rail-collapsed .nav-item .nav-label,
body.rail-collapsed .rail-collapse-label-expanded { display: none; }
body.rail-collapsed .rail-collapse { justify-content: center; }
body.rail-collapsed .rail-collapse .rail-collapse-label-collapsed {
  display: inline; }

/* Hamburger (phones): shown <768px; the sidebar becomes an overlay sheet. */
.rail-toggle { display: none; position: fixed; left: var(--sp-2);
  top: var(--sp-2); z-index: calc(var(--z-scrim) - 1);
  min-width: 44px; min-height: 44px;
  background: var(--bg-panel); border: 1px solid var(--line-strong);
  border-radius: var(--radius-md); color: var(--text-primary);
  font-size: var(--fs-16); cursor: pointer; }
.rail-scrim { position: fixed; inset: 0; background: var(--bg-overlay);
  z-index: var(--z-scrim); }

/* ---------- topbar (rules 2–6) ---------- */
#topbar { display: flex; align-items: center; gap: var(--sp-5);
  min-height: var(--topbar-h); padding: 0 var(--sp-5);
  background: var(--bg-panel); border-bottom: 1px solid var(--line-hairline);
  position: sticky; top: 0; z-index: 10; }
.view-title { font-size: var(--fs-20); font-weight: 600; }
.status-chips { display: flex; align-items: center; gap: var(--sp-2); }
.topbar-actions { display: flex; gap: var(--sp-2); margin-left: auto;
  align-items: center; }

/* Live indicator: the dashboard's honesty light. The dot is CSS content;
   the label text is set by app.js (setLive). Rule 4: three-state chip. */
.live { display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-12); color: var(--text-secondary);
  padding: var(--sp-1) var(--sp-3);
  background: var(--bg-card); border: 1px solid var(--line-hairline);
  border-radius: var(--radius-full); }
.live-dot::before { content: "●"; font-size: var(--fs-11); }
.live[data-mode="live"] { color: var(--state-ok); }
.live[data-mode="reconnecting"] { color: var(--state-warn); }
.live[data-mode="polling"] { color: var(--text-secondary); }

/* Status chips rendered by views.js (heartbeat / limiter headroom).
   Same chip skin; views.js owns honesty of the values. */
.chip-status { display: inline-flex; align-items: center; gap: var(--sp-2);
  font-size: var(--fs-12); color: var(--text-secondary);
  padding: var(--sp-1) var(--sp-3);
  background: var(--bg-card); border: 1px solid var(--line-hairline);
  border-radius: var(--radius-full);
  /* long values (lane list) must never stretch the top bar: the value
     ellipsizes, the chip box itself can shrink below its content. */
  max-width: 300px; min-width: 0; }
.chip-status > span:last-child { min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.chip-status.ok { color: var(--state-ok); }
.chip-status.bad { color: var(--sev-critical); }
.chip-status .chip-label { color: var(--text-primary); }

/* KPI rail (rule 5): same nodes; hidden at ≥768px — their visible home
   is the Dashboard metric cards (renderStats keeps writing these ids).
   Below 768px the rail doubles as the compact stats strip. */
.kpi { display: flex; gap: var(--sp-2); color: var(--text-secondary);
  font-size: var(--fs-12); flex-wrap: wrap; }
.kpi b { color: var(--text-primary); font-size: var(--fs-16);
  font-variant-numeric: tabular-nums; }
.kpi-crit b { color: var(--sev-critical); }
.kpi-ver b { color: var(--state-ok); }
.kpi-risk b { color: var(--wallet); }

/* Buttons (component spec §3.5): ghost / solid / danger.
   Height 32px (compact) / 40px (primary); 44px hit target on coarse
   pointers. */
button.ghost, .ghost {
  background: none; border: 1px solid var(--line-strong);
  color: var(--text-secondary); border-radius: var(--radius-md);
  padding: var(--sp-2) var(--sp-3); min-height: 32px; cursor: pointer;
  font: var(--fs-13)/1 var(--font-ui);
  transition: background var(--motion-fast) var(--ease),
              color var(--motion-fast) var(--ease); }
button.ghost:hover, .ghost:hover { color: var(--text-primary);
  background: var(--bg-elevated); }
.btn-solid { background: var(--accent); color: var(--on-accent); border: 0;
  border-radius: var(--radius-md); cursor: pointer; font-weight: 600;
  font-size: var(--fs-14); min-height: 40px; padding: var(--sp-2) var(--sp-4);
  box-shadow: var(--elev-1);
  transition: background var(--motion-fast) var(--ease); }
.btn-solid:hover { background: var(--accent-hover); }
.ghost.danger { border-color: var(--sev-critical); color: var(--sev-critical); }
.ghost.danger:hover { background: rgba(255,123,114,.10);
  color: var(--sev-critical); }

/* ---------- dashboard (metric grid, §2.2 + §2.4) ---------- */
.view { display: grid; gap: var(--sp-5); padding: var(--sp-5);
  align-content: start;
  /* minmax(0,1fr): the implicit column must never blow out to a child's
     min-content (the filters bar did exactly that at 1024px — the track
     sized to the unwrapped bar and the page scrolled sideways). */
  grid-template-columns: minmax(0, 1fr); }
.metric-grid { display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.metric-card { display: flex; flex-direction: column; gap: var(--sp-1);
  background: var(--bg-card); border: 1px solid var(--line-hairline);
  border-radius: var(--radius-lg); padding: var(--sp-4);
  box-shadow: var(--elev-1); }
.metric-card .metric-label { color: var(--text-secondary);
  font-size: var(--fs-12); text-transform: uppercase;
  letter-spacing: .05em; }
.metric-card .metric-value { display: block; font-size: var(--fs-32);
  font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.2; }
.metric-card .metric-hint { color: var(--text-secondary);
  font-size: var(--fs-12); }
.metric-card.critical { border-left: 3px solid var(--sev-critical); }
.metric-card.critical .metric-value { color: var(--sev-critical); }
.metric-card.ok .metric-value { color: var(--state-ok); }
.metric-card.wallet .metric-value { color: var(--wallet); }
.metric-card.bad .metric-value { color: var(--sev-critical); }

/* Interactive metric cards are the KPI buttons themselves (same node
   ids, so renderStats() keeps writing them untouched). */
button.metric-card, button.kpi-cell.metric-card { cursor: pointer;
  font: inherit; text-align: left;
  background: var(--bg-card); color: inherit; }
button.metric-card:hover, button.kpi-cell.metric-card:hover {
  background: var(--bg-elevated); }

/* Compact metric cards (kpi-card idiom, rule 20). */
.kpi-row { display: grid; gap: var(--sp-3);
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  margin-bottom: var(--sp-4); }
.kpi-card { display: flex; flex-direction: column; gap: var(--sp-1);
  background: var(--bg-card); border: 1px solid var(--line-hairline);
  border-radius: var(--radius-lg); padding: var(--sp-3);
  text-align: center; }
.kpi-card b { display: block; font-size: var(--fs-24);
  font-variant-numeric: tabular-nums; }
.kpi-card span { color: var(--text-secondary); font-size: var(--fs-11); }
.kpi-card.ok b { color: var(--state-ok); }
.kpi-card.bad b { color: var(--sev-critical); }

/* Limiter progress bars (§2.4). Fill color by threshold:
   ≥50% ok · 10–50% warn · <10% err. */
.progress-row { display: grid; gap: var(--sp-1); margin: var(--sp-3) 0; }
.progress-head { display: flex; justify-content: space-between;
  font-size: var(--fs-12); color: var(--text-secondary); }
.progress-track { height: 8px; border-radius: var(--radius-full);
  background: var(--line-hairline); overflow: hidden; }
.progress-fill { height: 100%; border-radius: var(--radius-full);
  background: var(--state-ok); }
.progress-fill.warn { background: var(--state-warn); }
.progress-fill.err { background: var(--state-err); }

/* ---------- cards (component spec §3.5) ---------- */
.card { background: var(--bg-card); border: 1px solid var(--line-hairline);
  border-radius: var(--radius-lg); padding: var(--sp-4);
  box-shadow: var(--elev-1); }
.card h3 { font-size: var(--fs-16); margin-bottom: var(--sp-3); }
.view-grid-2 { display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  align-items: start; }

/* ---------- filters (rules 7–9) ---------- */
.filters { display: flex; gap: var(--sp-3); flex-wrap: wrap;
  align-items: center; padding: var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--line-hairline); }
.chips { display: flex; gap: var(--sp-1); flex-wrap: wrap; }
.chip { background: none; border: 1px solid var(--line-strong);
  color: var(--text-secondary); border-radius: var(--radius-full);
  padding: var(--sp-1) var(--sp-3); cursor: pointer;
  font-size: var(--fs-12);
  transition: background var(--motion-fast) var(--ease),
              color var(--motion-fast) var(--ease); }
.chip:hover { color: var(--text-primary); background: var(--bg-elevated); }
.chip.active { background: var(--accent-weak); color: var(--accent-text);
  border-color: var(--accent); }
.chip-critical.active { border-color: var(--sev-critical);
  color: var(--sev-critical); background: rgba(255,123,114,.10); }
.chip-high.active { border-color: var(--sev-high); color: var(--sev-high);
  background: rgba(245,169,92,.10); }
.chip-medium.active { border-color: var(--sev-medium);
  color: var(--sev-medium); background: rgba(232,197,91,.10); }
.chip-low.active { border-color: var(--sev-low); color: var(--text-primary);
  background: rgba(139,147,167,.12); }
.chip-wallet.active { border-color: var(--wallet); color: var(--wallet);
  background: rgba(242,139,184,.12); }
select, #f-search, #token-input { height: 36px;
  padding: var(--sp-2) var(--sp-3);
  background: var(--bg-panel); color: var(--text-primary);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  font-size: var(--fs-13); }
#f-search { margin-left: auto; min-width: 320px; }
/* round-58 GitHub token console (PAT cluster): the candidate field
   carries a raw secret, so it is a password input (masked on screen;
   the value is never echoed back by the app). Token-ized per rule 17. */
#token-input { width: 100%; font-family: var(--font-mono);
  margin: var(--sp-2) 0 var(--sp-3); }
#tab-token p { color: var(--text-secondary); font-size: var(--fs-12);
  line-height: 1.5; margin: 0 0 var(--sp-3); }

/* ---------- table (rules 10–12) ---------- */
main { padding: 0 var(--sp-5) var(--sp-5); }
.table-card { overflow: auto;
  max-height: calc(100dvh - var(--topbar-h) - 140px); }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; color: var(--text-secondary); font-size: var(--fs-11);
  font-weight: 600; text-transform: uppercase; letter-spacing: .05em;
  padding: var(--sp-2); border-bottom: 1px solid var(--line-hairline); }
#findings thead th { position: sticky; top: 0; background: var(--bg-card);
  z-index: 1; }
/* round-16: 44px minimum row height — the SOC table is operated with a
   mouse in bursts and on tablets; 34px rows were mis-clicks waiting to
   happen. */
td { padding: var(--sp-2) var(--sp-3); vertical-align: middle;
  border-bottom: 1px solid var(--line-hairline); }
tbody td { height: 44px; }
tbody tr { cursor: pointer; }
tbody tr:hover { background: var(--bg-elevated); }
/* §2.3: the focused (selected) row carries an accent left rail on top of
   the focus ring — keyboard operators always see which finding owns the
   drawer trigger. */
#findings tbody tr[data-fp]:focus-visible {
  box-shadow: inset 2px 0 0 var(--accent);
  outline-offset: -2px; }
#findings .row-age, #findings .row-hits { font-variant-numeric: tabular-nums;
  font-size: var(--fs-13); }

/* round-16: readable pills. 10px uppercase micro-text at SOC viewing
   distance was decoration, not data; 11px with slightly looser tracking
   stays compact and becomes legible. §3.2: triple channel — color +
   uppercase label + pill border; dot is CSS content, never alone. */
.sev-pill { display: inline-flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-11); font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-full); border: 1px solid transparent; }
.sev-pill::before { content: ""; width: 6px; height: 6px;
  border-radius: var(--radius-full); background: currentColor; }
.sev-pill-critical { background: rgba(255,123,114,.10);
  color: var(--sev-critical); border-color: rgba(255,123,114,.4); }
.sev-pill-high { background: rgba(245,169,92,.10); color: var(--sev-high);
  border-color: rgba(245,169,92,.35); }
.sev-pill-medium { background: rgba(232,197,91,.10); color: var(--sev-medium);
  border-color: rgba(232,197,91,.3); }
.sev-pill-low { background: rgba(139,147,167,.12); color: var(--sev-low);
  border-color: rgba(139,147,167,.3); }
.sev-critical { color: var(--sev-critical); font-weight: 700; }
.sev-high { color: var(--sev-high); font-weight: 600; }
.sev-medium { color: var(--sev-medium); }
.sev-low { color: var(--sev-low); }
.mask { font-family: var(--font-mono); font-size: var(--fs-12);
  color: var(--text-secondary); }
.dim { color: var(--text-secondary); }
.ver { color: var(--state-ok); font-size: var(--fs-11); }
.wallet { font-size: var(--fs-11); margin-top: 3px; font-weight: 600; }
.wallet-funded { color: var(--wallet); font-weight: 700; }
.wallet-empty { color: var(--text-secondary); font-weight: 400; }
.wallet-unknown { color: var(--text-secondary); font-style: italic;
  font-weight: 400; }
a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }
.repo-brief { color: var(--text-secondary); font-size: var(--fs-12);
  display: inline-block; max-width: 340px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.repo-meta { color: var(--text-secondary); font-size: var(--fs-12);
  margin-top: var(--sp-1); }
.repo-count { color: var(--accent-text); font-size: var(--fs-11);
  margin-left: var(--sp-3); border: 1px solid var(--accent);
  border-radius: var(--radius-full); padding: 2px var(--sp-2);
  font-variant-numeric: tabular-nums; }
.repo-head { cursor: pointer; }
.repo-head td { background: var(--bg-panel);
  border-top: 2px solid var(--line-strong);
  padding-top: var(--sp-3); padding-bottom: var(--sp-3); }
.repo-head:hover td { background: var(--bg-elevated); }
.repo-head .repo-brief { max-width: 480px; }
/* rule 12: chevron indicator on the repo header. */
.repo-head td:first-child::before { content: "▾ "; font-size: var(--fs-11);
  color: var(--text-secondary); }
.repo-head.repo-collapsed td:first-child::before { content: "▸ "; }
.repo-body-hidden { display: none; }
.repo-collapsed td { opacity: 1; }
.toast-more { color: var(--accent-text); font-weight: 700;
  margin-left: var(--sp-2); }
#toasts { max-width: 380px; }

/* ---------- runtime console internals (rules 19–20) ---------- */
.tabs { display: flex; gap: var(--sp-1); flex-wrap: wrap;
  border-bottom: 1px solid var(--line-hairline); margin-bottom: var(--sp-4); }
.tab { background: none; border: none; color: var(--text-secondary);
  padding: var(--sp-2) var(--sp-4); cursor: pointer;
  font: var(--fs-13)/1 var(--font-ui);
  border-bottom: 2px solid transparent; }
.tab:hover { color: var(--text-primary); }
.tab.active { color: var(--accent-text); border-bottom-color: var(--accent); }
.tab-body { overflow: auto; }
.mono { font-size: var(--fs-12); white-space: pre-wrap;
  word-break: break-all; color: var(--text-secondary); }
table.mini { width: 100%; border-collapse: collapse; font-size: var(--fs-12); }
table.mini th { font-size: var(--fs-11); text-transform: uppercase;
  letter-spacing: .05em; }
table.mini td, table.mini th { padding: var(--sp-1) var(--sp-2);
  border-bottom: 1px solid var(--line-hairline);
  text-align: left; font-variant-numeric: tabular-nums; }
.halt { color: var(--sev-critical);
  background: rgba(255,123,114,.08);
  border: 1px solid rgba(255,123,114,.4);
  border-radius: var(--radius-md); padding: var(--sp-3);
  margin: var(--sp-3) 0; font-size: var(--fs-13); }
.tab-body h4, .card h4 { color: var(--text-secondary);
  font-size: var(--fs-12); text-transform: uppercase; letter-spacing: .05em;
  margin: var(--sp-4) 0 var(--sp-2); }
.cred-var code { background: var(--bg-panel); padding: 2px var(--sp-2);
  border-radius: var(--radius-sm); font-size: var(--fs-12);
  color: var(--accent-text); }
.oneline { display: inline-block; max-width: 260px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
td .mask + .wallet, td .oneline + .wallet { margin-top: 2px; }
.badge { display: inline-block; font-size: var(--fs-11);
  padding: 2px var(--sp-2); border-radius: var(--radius-full);
  border: 1px solid var(--line-strong); color: var(--text-primary);
  background: rgba(154,165,184,.12); letter-spacing: .02em; }
.badge-new { border-color: var(--accent); color: var(--accent-text);
  background: var(--accent-weak); }
.badge-triaged { border-color: var(--accent); color: var(--accent-text);
  background: var(--accent-weak); }
.badge-confirmed { border-color: var(--sev-high); color: var(--sev-high);
  background: rgba(245,169,92,.12); }
.badge-false_positive { border-color: var(--line-strong);
  color: var(--text-secondary); background: rgba(154,165,184,.12); }
.badge-reported { border-color: var(--sev-high); color: var(--sev-high);
  background: rgba(245,169,92,.12); }
.badge-rotated { border-color: var(--state-ok); color: var(--state-ok);
  background: rgba(107,212,140,.12); }

/* ---------- drawer / sheets (rule 16, sheet spec §3.5) ---------- */
#scrim { position: fixed; inset: 0; background: var(--bg-overlay);
  z-index: var(--z-scrim); }
.drawer { position: fixed; top: 0; right: 0; bottom: 0;
  width: min(560px, 94vw); background: var(--bg-panel);
  border-left: 1px solid var(--line-hairline); z-index: var(--z-sheet);
  display: flex; flex-direction: column; box-shadow: var(--elev-3);
  animation: sheet-in var(--motion-slow) var(--ease); }
@keyframes sheet-in {
  from { transform: translateX(24px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}
.drawer-head { display: flex; align-items: center;
  min-height: 48px; padding: var(--sp-3) var(--sp-5);
  border-bottom: 1px solid var(--line-hairline);
  font-size: var(--fs-16); font-weight: 600; }
.drawer-head button { margin-left: auto; }
.card-head { padding: 0; border-bottom: 0; margin-bottom: var(--sp-3); }
.card-head h3 { margin: 0; }
#d-body { flex: 1; overflow-y: auto; padding: var(--sp-4) var(--sp-5); }
.kv { display: grid; grid-template-columns: 130px 1fr;
  gap: var(--sp-2) var(--sp-4); font-size: var(--fs-13);
  margin-bottom: var(--sp-4); }
.kv dt { color: var(--text-secondary); }
.kv dd { margin: 0; word-break: break-word; }
.note { background: var(--bg-card); border: 1px solid var(--line-hairline);
  border-radius: var(--radius-md); padding: var(--sp-3);
  font-size: var(--fs-13); color: var(--text-primary); margin: var(--sp-2) 0;
  border-left: 3px solid var(--line-strong); }
.note-cap { border-left-color: var(--accent); }
.note-blast { border-left-color: var(--sev-high); }
.note-rev { border-left-color: var(--state-ok); }
.drawer-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap;
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--line-hairline); }

/* ---------- toasts (rule 21: bottom-RIGHT) ---------- */
#toasts { position: fixed; bottom: var(--sp-4); right: var(--sp-4);
  display: grid; gap: var(--sp-2); z-index: var(--z-toast); }
.toast { background: var(--bg-elevated); border: 1px solid var(--line-hairline);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-md); padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-13); max-width: 380px; box-shadow: var(--elev-2); }
.toast-critical { border-left-color: var(--sev-critical); }

/* ---------- round-16: query UX (live dot, sorting, paging, states) ---- */

/* Keyboard focus (rule 24): one ring, one color, on every interactive
   target. :focus-visible only — mouse clicks stay clean, keyboard
   operators always see the caret. */
button:focus-visible, select:focus-visible, input:focus-visible,
a:focus-visible, [tabindex]:focus-visible, tr[data-fp]:focus-visible,
a.nav-item:focus-visible, #sidenav button:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Clickable KPI rail (shortcuts documented in the title/tooltip).
   round-30 R30-WEB-03: the three interactive cells are native <button>s
   (round-19 doctrine — never a tabindex=0 <span> fake), so the UA button
   skin is reset to the quiet text rail it replaced (the .sort-btn idiom).
   .kpi-risk stays a <span>: it carries no action, so it must not invite a
   click or take the caret. */
.kpi-cell { border-radius: var(--radius-md); padding: var(--sp-1) var(--sp-2); }
button.kpi-cell { cursor: pointer;
  background: none; border: 0; color: inherit; font: inherit;
  text-transform: inherit; letter-spacing: inherit; }
button.kpi-cell:hover { background: var(--accent-weak); }

/* Sortable column headers: quiet by default, one clear accent when active. */
.th-sort { padding: 0; }
.sort-btn { background: none; border: 0; color: inherit; font: inherit;
  text-transform: inherit; letter-spacing: inherit; cursor: pointer;
  padding: var(--sp-2) var(--sp-3); display: inline-flex;
  align-items: center; gap: var(--sp-1); }
.sort-btn:hover { color: var(--text-primary); }
.sort-ind::before { content: "↕"; opacity: .35; font-size: var(--fs-11); }
th[aria-sort="ascending"] .sort-ind::before { content: "↑"; opacity: 1;
  color: var(--accent-text); }
th[aria-sort="descending"] .sort-ind::before { content: "↓"; opacity: 1;
  color: var(--accent-text); }

/* Pager (rule 15): findings card footer bar, left-aligned. */
.pager { display: flex; align-items: center; gap: var(--sp-4);
  padding: var(--sp-3) 0; }
.pager-info { color: var(--text-secondary); font-size: var(--fs-13);
  font-variant-numeric: tabular-nums; }
button:disabled { opacity: .4; cursor: default; }
button:disabled:hover { color: var(--text-disabled); background: none; }

/* Density toggle (Settings): compact rows for keyboard operators. */
html.density-compact tbody td { height: var(--sp-6); }
html.density-compact .skeleton-row td { height: var(--sp-6); }

/* Loading: cold loads render skeleton rows, never a blank void. */
.skeleton-row td { height: 44px; }
.skeleton-cell { display: block; height: 14px;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--line-hairline) 25%,
              var(--bg-elevated) 50%, var(--line-hairline) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.2s ease-in-out infinite; }
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Error (rule 13): the table stays, visibly stale; the card says what
   broke. */
#findings.stale { opacity: .5; }
.err-card { display: flex; flex-direction: column; gap: var(--sp-1);
  margin: var(--sp-3) 0; padding: var(--sp-4);
  border: 1px solid rgba(255,123,114,.4);
  background: rgba(255,123,114,.08);
  border-radius: var(--radius-md); }
.err-card b { color: var(--sev-critical); }
.err-card .dim { color: var(--text-secondary); font-size: var(--fs-12); }

/* Empty (rule 14): centered on --bg-card, icon slot, secondary text. */
.empty { color: var(--text-secondary); text-align: center;
  background: var(--bg-card); border-radius: var(--radius-lg);
  padding: var(--sp-8); }
.empty-icon { font-size: var(--fs-32); display: block;
  margin-bottom: var(--sp-3); }
.empty-actions { display: flex; justify-content: center;
  padding: var(--sp-3) 0 var(--sp-6); }

/* Shortcut help panel (rule 18): sheet spec. */
.help-panel { position: fixed; top: 0; right: 0; bottom: 0;
  width: min(380px, 90vw); background: var(--bg-panel);
  border-left: 1px solid var(--line-hairline); z-index: var(--z-sheet);
  overflow-y: auto; box-shadow: var(--elev-3);
  animation: sheet-in var(--motion-slow) var(--ease); }
.keys { display: grid; grid-template-columns: auto 1fr;
  gap: var(--sp-2) var(--sp-4); padding: var(--sp-4) var(--sp-5);
  font-size: var(--fs-13); }
.keys dt { font-family: var(--font-mono); color: var(--accent-text); }
.keys dd { margin: 0; color: var(--text-secondary); }
kbd { background: var(--bg-card); border: 1px solid var(--line-strong);
  border-radius: var(--sp-1); padding: 1px 5px; font-size: var(--fs-11);
  font-family: var(--font-mono); }

/* Occurrence ledger in the drawer (round-16: the full list, not one row). */
.occ-table { width: 100%; margin-top: var(--sp-2); font-size: var(--fs-12);
  border-collapse: collapse; }
.occ-table th { text-align: left; color: var(--text-secondary);
  font-size: var(--fs-11); font-weight: 600; text-transform: uppercase;
  letter-spacing: .05em; padding: var(--sp-1) var(--sp-2);
  border-bottom: 1px solid var(--line-hairline); }
.occ-table td { height: auto; padding: var(--sp-1) var(--sp-2);
  border-bottom: 1px solid var(--line-hairline); }

/* ---------- responsive (UIUX_SPEC §5) ---------- */

/* ≥768px: the KPI rail yields to the metric cards (rule 5). */
@media (min-width: 768px) {
  .kpi { display: none; }
}

/* 768–1279px: metric grid 2-across handled by auto-fill; the "Code age"
   and "Hits" columns fold away (headers AND cells hidden — no orphan
   aria-sort button, since the button lives inside the hidden th).
   The runtime chips (heartbeat/limiter/lanes) yield to the narrow bar —
   their truth lives on the Dashboard cards; the honest live chip stays. */
@media (min-width: 768px) and (max-width: 1279px) {
  .chip-status { display: none; }
  #findings .col-age, #findings .col-hits { display: none; }
  #findings tbody tr:not(.repo-head) td:nth-child(5),
  #findings tbody tr:not(.repo-head) td:nth-child(7) { display: none; }
  .table-card { max-height: none; }
}

/* <768px (phone/emergency): rail hidden until the hamburger opens it as
   an overlay sheet; top bar actions collapse to icons; the table stacks
   into cards; filters scroll horizontally. */
@media (max-width: 767px) {
  .app-shell { display: block; }
  #sidenav { position: fixed; left: 0; top: 0; bottom: 0;
    transform: translateX(-100%); z-index: calc(var(--z-scrim) + 1);
    flex-basis: var(--rail-w); width: var(--rail-w); }
  body.nav-open #sidenav { transform: translateX(0);
    box-shadow: var(--elev-3); }
  body:not(.nav-open) .rail-scrim { display: none; }
  .rail-toggle { display: inline-flex; align-items: center;
    justify-content: center; }
  #topbar { padding-left: 60px; padding-right: var(--sp-3);
    gap: var(--sp-2); }
  .view-title { font-size: var(--fs-16); min-width: 0; flex-shrink: 1;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .status-chips { gap: var(--sp-2); flex-shrink: 0; }
  /* §5: runtime chips (heartbeat/limiter/lanes) yield to the phone —
     their truth lives on the Dashboard cards; the honest live chip
     stays. The live TEXT is sr-only here (the dot + title/aria-label
     carry the state visually; the text still announces it). */
  .chip-status { display: none; }
  .live { font-size: var(--fs-11); padding: var(--sp-1) var(--sp-2); }
  .live #live-text { position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .topbar-actions { gap: var(--sp-1); flex-shrink: 0; }
  .topbar-actions .btn-label { display: none; }
  .topbar-actions button { min-width: 40px; min-height: 44px;
    text-align: center; padding: var(--sp-2); }
  .filters { flex-wrap: nowrap; overflow-x: auto; }
  #f-search { min-width: 180px; margin-left: 0; }
  .view, main { padding-left: var(--sp-3); padding-right: var(--sp-3); }
  #findings, #findings tbody, #findings tr, #findings td, #findings th {
    display: block; }
  #findings thead { display: none; }
  #findings tbody tr { border: 1px solid var(--line-hairline);
    border-radius: var(--radius-md); margin: var(--sp-2) 0;
    background: var(--bg-card); padding: var(--sp-2); }
  #findings tbody tr:hover { background: var(--bg-card); }
  #findings td { border: 0; padding: 2px var(--sp-2); }
  #findings tbody td { height: auto; }
  #findings td::before { content: attr(data-label);
    display: inline-block; min-width: 90px; color: var(--text-secondary);
    font-size: var(--fs-11); text-transform: uppercase;
    letter-spacing: .05em; }
  #findings .repo-head td { background: var(--bg-card); border: 0;
    padding: var(--sp-2); }
  #findings .repo-head td::before { display: none; }
  /* §5: the pager sticks to the viewport bottom while the stacked table
     scrolls (opaque background, hairline top edge). */
  .pager { position: sticky; bottom: 0; z-index: 4;
    background: var(--bg-card);
    border-top: 1px solid var(--line-hairline); }
}

/* Coarse pointers: min 44×44 hit targets on every control (§3.5). */
@media (pointer: coarse) {
  button.ghost, .chip, .nav-item, .sort-btn, .kpi-cell, .tab {
    min-height: 44px; min-width: 44px; }
  #f-search, select { min-height: 44px; }
}

/* Accessibility: operators who asked for reduced motion get a static UI.
   The shimmer stops; transitions collapse; nothing else changes.
   (Rule 24: this block stays last.) */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .skeleton-cell { background: var(--line-hairline); }
}
