/* vuln-monitor dashboard stylesheet.
   Extracted from static/dashboard.html so the page stops carrying 800 lines of
   inline CSS: the file had two design systems stacked on top of each other and
   no way to tell, while editing, which layer a rule belonged to.

   Load order matters. The "late overrides" section at the bottom used to be a
   second <style> block after </script>, and several rules there deliberately
   beat identical selectors above. Keep it last. */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* Design tokens live in css/tokens.css, loaded before this file and shared
   with detail.html and new.html. */

body {
  background: var(--bg);
  color: var(--body);
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 400; line-height: 1.5; min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--ink); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ── User menu ── */
.user-menu { position: relative; }
.user-btn {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface); border: 1px solid var(--ink); border-radius: var(--pill);
  padding: 6px 14px; font-family: inherit; font-size: 13px; font-weight: 600;
  color: var(--ink); cursor: pointer; box-shadow: var(--shadow-soft);
}
.user-btn:hover { background: var(--surface-3); }
.user-dropdown {
  position: absolute; right: 0; top: calc(100% + 8px);
  background: var(--surface); border: 1px solid var(--ink); border-radius: 12px;
  box-shadow: var(--shadow-hard); min-width: 160px; padding: 6px;
  display: none; flex-direction: column; gap: 2px;
}
.user-dropdown.open { display: flex; }
.user-dropdown-item {
  background: transparent; border: none; border-radius: 8px;
  padding: 8px 12px; text-align: left; font-family: inherit; font-size: 13px;
  color: var(--ink); cursor: pointer;
}
.user-dropdown-item:hover { background: var(--bg); }
.user-dropdown-hr { border: none; border-top: 1px solid var(--line); margin: 4px 0; }

/* ── Pill-row filters ── */
.filter-row { max-width: 1260px; margin: 10px auto 0; padding: 0 40px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
#timeRow { margin-top: 36px; }
.group-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; font-weight: 600; min-width: 64px; }

.meta-row { max-width: 1260px; margin: 18px auto 0; padding: 0 40px; display: flex; align-items: baseline; justify-content: center; gap: 18px 28px; flex-wrap: wrap; }
.stats { display: flex; gap: 28px; align-items: baseline; flex-wrap: wrap; }
.stat-item { display: inline-flex; align-items: baseline; gap: 8px; }
.stat-item + .stat-item { padding-left: 28px; border-left: 1px solid var(--ink); }
.stat-num { font-size: 28px; font-weight: 800; color: var(--ink); line-height: 1; letter-spacing: -.02em; }
.stat-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; font-weight: 600; white-space: nowrap; }
@media (max-width: 900px) { .nav-tagline { display: none; } }

/* ── Pushed toggle ── */
.pushed-toggle { display: inline-flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--ink); cursor: pointer; user-select: none; }
.pushed-toggle input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
.pushed-toggle .switch {
  width: 36px; height: 20px; background: var(--surface); border: 1px solid var(--ink);
  border-radius: var(--pill); position: relative; transition: background .25s var(--spring);
}
.pushed-toggle input:focus-visible + .switch { outline: 2px solid var(--focus); outline-offset: 3px; }
.pushed-toggle .switch::after {
  content: ''; position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; background: var(--ink); border-radius: 50%;
  transition: transform .25s var(--spring);
}
.pushed-toggle input:checked + .switch { background: var(--neutral-4); }
.pushed-toggle input:checked + .switch::after { transform: translateX(16px); }

/* ── Theme toggle ── */
.theme-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--ink); background: var(--surface);
  color: var(--ink); cursor: pointer; font-size: 16px;
  box-shadow: var(--shadow-soft); transition: transform .2s var(--spring);
}
.theme-btn:hover { transform: translateY(-2px); background: var(--surface-3); }

/* ── Category pills ── */
.cat-row { max-width: 1260px; margin: 10px auto 0; padding: 0 40px; display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.cat-pill {
  font-family: inherit; line-height: 1.5;
  padding: 6px 18px; border-radius: var(--pill); border: 1px solid var(--ink);
  background: var(--surface); font-size: 12px; font-weight: 600; color: var(--ink);
  cursor: pointer; transition: all .25s var(--spring); user-select: none;
}
.cat-pill:hover { background: var(--surface-3); transform: translateY(-2px); box-shadow: var(--shadow-soft); }
.cat-pill:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }
.cat-pill.active { background: var(--ink); color: var(--surface-3); }

/* ── Vuln cards ── */
.grid { max-width: 1260px; margin: 24px auto 0; padding: 0 40px 60px; display: grid; grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); gap: 20px; }
.vcard {
  background: var(--surface); border: 1px solid var(--ink); border-radius: var(--radius);
  padding: 22px 24px 20px; display: flex; flex-direction: column; gap: 10px;
  transition: transform .25s var(--spring), box-shadow .25s var(--spring);
  position: relative;
  overflow-wrap: anywhere; min-width: 0; cursor: pointer;
}
.vcard:hover { transform: translateY(-4px); box-shadow: var(--shadow-hard); }
.vcard-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.vcard-date { margin-left: auto; font-size: 12px; color: var(--muted); font-family: 'JetBrains Mono', monospace; font-weight: 500; }
.src-badge {
  display: inline-flex; align-items: center; padding: 3px 12px; border-radius: var(--pill);
  border: 1px solid var(--ink);
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px;
}
.reason-badge {
  padding: 2px 10px; border-radius: var(--pill); border: 1px solid var(--ink);
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .3px;
}
/* .sev-badge is NOT defined here — see the single "Severity" block near the end
   of this file. It used to be declared three times across this file's layers
   with three different palettes, and only the last one rendered. */
.exploit-badge.none { background: #e5e7eb; color: var(--ink); border-color: var(--muted); }
.pr-badge {
  padding: 2px 8px; border-radius: var(--pill); font-size: 10px; font-weight: 700;
  letter-spacing: .3px; font-family: 'JetBrains Mono', monospace;
  background: #DC2626; color: #fff; border: 1px solid #991B1B;
}
/* EPSS exploit-probability badge. Outline-only (border = currentColor) so it
   reads on both the dark and light themes without per-theme overrides. */
.epss-badge {
  padding: 2px 8px; border-radius: var(--pill); font-size: 10px; font-weight: 700;
  letter-spacing: .3px; font-family: 'JetBrains Mono', monospace;
  background: transparent; border: 1px solid currentColor; color: var(--muted);
}
.epss-badge.epss-mid { color: var(--sev-high-line); }
.epss-badge.epss-hi  { color: var(--danger); font-weight: 800; }

/* Analytics modal charts — inline SVG + div bars, theme-safe via currentColor. */
#analyticsModal .nm-card { width: 640px; max-width: 92vw; max-height: 86vh;
  display: flex; flex-direction: column; }
.analytics-range { display: flex; gap: 6px; margin: 4px 0 14px; flex: 0 0 auto; }
.analytics-body { overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.chart-block { margin-bottom: 20px; }
.chart-title { font-size: 11px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); margin-bottom: 3px; }
.chart-desc { font-size: 11.5px; color: var(--muted); line-height: 1.5; margin: 0 0 8px; }
.analytics-intro { font-size: 12px; color: var(--body); line-height: 1.55; margin: 0 0 14px; }
.trend-svg { width: 100%; height: 64px; color: var(--ink); display: block; }
.trend-svg rect { fill: currentColor; }
.hbar-row { display: flex; align-items: center; gap: 8px; margin: 3px 0; font-size: 12px; }
.hbar-label { flex: 0 0 96px; text-align: right; color: var(--body); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.hbar-track { flex: 1 1 auto; height: 12px; background: rgba(127,127,127,0.14); border-radius: 6px; overflow: hidden; }
.hbar-fill { display: block; height: 100%; background: currentColor; color: var(--ink); border-radius: 6px; }
.hbar-val { flex: 0 0 auto; min-width: 28px; color: var(--muted);
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 11px; }
.analytics-empty { font-size: 12px; color: var(--muted); padding: 8px 0; }
.nm-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.tag-chip { border: 1.5px solid var(--ink); border-radius: var(--pill); padding: 4px 12px; font-size: 12px; cursor: pointer; background: var(--surface); color: var(--ink); font-family: inherit; }
.tag-chip.on { background: var(--neutral-3); color: #fff; border-color: var(--neutral-3); }
.tag-chip:hover { opacity: .85; }
.tag-input { border: 1.5px solid var(--ink); border-radius: 8px; padding: 3px 8px; font-size: 12px; font-family: inherit; width: 120px; }
.tag-add { border: 1.5px solid var(--ink); border-radius: 8px; padding: 3px 10px; cursor: pointer; background: var(--bg); color: var(--ink); font-size: 13px; line-height: 1; }
.tag-x { opacity: .65; font-size: 10px; }
.pushed-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.src-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px; vertical-align: middle; background: var(--muted); flex: 0 0 auto; }
.src-dot.ok { background: var(--neutral-4); box-shadow: 0 0 0 2px rgba(57,191,151,.25); }
.src-dot.bad { background: #dc2626; box-shadow: 0 0 0 2px rgba(220,38,38,.25); animation: srcDotPulse 1.6s ease-in-out infinite; }
@keyframes srcDotPulse { 50% { box-shadow: 0 0 0 4px rgba(220,38,38,.18); } }

/* ── Modals ── */
.modal {
  position: fixed; inset: 0; z-index: 1000;
  background: rgba(17,16,19,0.45);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.modal.hidden { display: none; }
.nm-card {
  background: var(--surface); border: 2px solid var(--ink); border-radius: var(--radius);
  box-shadow: var(--shadow-hard); padding: 20px 22px;
  width: 100%; max-width: 620px; max-height: 85vh; overflow: auto;
}
.nm-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
.nm-cve { font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 600; background: var(--line); color: var(--ink); padding: 2px 8px; border-radius: var(--pill); }
.nm-src { font-size: 12px; color: var(--muted); }
.nm-x { margin-left: auto; border: none; background: transparent; cursor: pointer; font-size: 16px; color: var(--ink); opacity: 0.5; padding: 0 4px; }
.nm-x:hover { opacity: 1; }
.nm-title { font-size: 16px; font-weight: 700; color: var(--ink); line-height: 1.4; margin-bottom: 6px; overflow-wrap: anywhere; }
.nm-link { font-size: 12px; color: var(--neutral-3); display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }
.nm-sep { border: none; border-top: 1px solid rgba(17,16,19,0.15); margin: 14px 0; }
.nm-label { font-size: 11px; color: var(--muted); margin-bottom: 6px; text-transform: uppercase; letter-spacing: .05em; }
.nm-edit-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 10px; }
.nm-count { font-size: 11px; color: var(--muted); font-family: 'JetBrains Mono', monospace; }
.nm-btn { border: 1.5px solid var(--ink); border-radius: var(--pill); padding: 5px 16px; cursor: pointer; background: var(--bg); font-family: inherit; font-size: 13px; color: var(--ink); }
.nm-btn:hover:not(:disabled) { background: var(--surface-3); }
.nm-btn:disabled { opacity: 0.35; cursor: default; }
.nm-btn.nm-primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.nm-btn.nm-primary:hover:not(:disabled) { opacity: 0.85; background: var(--ink); }
.nm-err { color: var(--neutral-1); font-size: 12px; margin-top: 8px; min-height: 14px; text-align: right; }
.detail-meta { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
.detail-field { font-size: 12px; color: var(--body); background: rgba(17,16,19,0.04); padding: 4px 10px; border-radius: 8px; }
.detail-field b { color: var(--ink); }
.detail-summary { font-size: 13px; color: var(--body); line-height: 1.6; background: rgba(17,16,19,0.03); padding: 10px; border-radius: 10px; }
.detail-llm {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12px; color: var(--body); line-height: 1.5;
  border-left: 3px solid var(--neutral-3); padding-left: 10px;
}
.detail-llm .llm-prefix {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 700;
  color: var(--neutral-3); letter-spacing: .5px;
}

.detail-analysis {
  font-size: 13px; color: var(--body); line-height: 1.6;
  background: rgba(17,16,19,0.03); padding: 12px; border-radius: 10px;
  max-height: 400px; overflow-y: auto;
}
.detail-analysis h1, .detail-analysis h2, .detail-analysis h3, .detail-analysis h4 { margin: 12px 0 6px; color: var(--ink); }
.detail-analysis code { background: rgba(17,16,19,0.08); padding: 2px 5px; border-radius: 4px; font-family: monospace; }

/* ── Lab verification ── */

/* ── Login modal ── */
.login-card {
  background: var(--surface); border: 2px solid var(--ink); border-radius: var(--radius);
  box-shadow: var(--shadow-hard); padding: 28px 26px;
  width: 100%; max-width: 360px; text-align: center;
}
.login-card .nav-logo { display: inline-block; margin-bottom: 18px; }
.login-card input {
  width: 100%; padding: 10px 14px; margin-bottom: 12px;
  border: 1.5px solid var(--ink); border-radius: 10px;
  font-family: inherit; font-size: 14px; background: var(--surface);
}
.login-card input:focus { outline: none; box-shadow: 0 0 0 3px rgba(17,16,19,0.10); }
.login-card button {
  width: 100%; padding: 10px; border: 1.5px solid var(--ink); border-radius: var(--pill);
  background: var(--ink); color: var(--bg); font-family: inherit; font-size: 14px; font-weight: 700;
  cursor: pointer;
}
.login-card button:hover { opacity: .9; }
.login-err { color: var(--neutral-1); font-size: 12px; margin-top: 10px; min-height: 14px; }
.login-hint { color: var(--muted); font-size: 12.5px; margin-bottom: 10px; line-height: 1.5; }
/* Recovery codes, shown exactly once. Monospace because they get transcribed. */
.rc-list {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px;
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 13px;
  background: var(--surface-2); border: 1px solid var(--line-strong);
  border-radius: 8px; padding: 12px; margin: 10px 0; color: var(--ink);
}
.sec-state { font-size: 12.5px; color: var(--body); margin-bottom: 8px; }
.sec-state b { color: var(--ink); }
.sec-uri {
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 10.5px;
  color: var(--muted); word-break: break-all; background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 6px; padding: 8px; margin: 8px 0;
}
#loginModal.modal { background: var(--bg); }

/* ── Settings modal ── */
.settings-grid { display: grid; gap: 12px; }
.settings-group-title { font-size: 14px; font-weight: 700; color: var(--ink); margin: 18px 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--line); }
.settings-group-title:first-child { margin-top: 0; }
.settings-row { display: grid; grid-template-columns: 180px 1fr; gap: 10px; align-items: center; }
.settings-row label { font-size: 12px; font-weight: 600; color: var(--ink); text-align: right; }
.settings-row input, .settings-row select {
  width: 100%; padding: 8px 10px; border: 1.5px solid var(--ink); border-radius: 8px;
  font-family: inherit; font-size: 13px; background: var(--surface);
  color: var(--ink);
}
.settings-row input:focus, .settings-row select:focus { outline: none; box-shadow: 0 0 0 3px rgba(17,16,19,0.10); }
.settings-row select { appearance: auto; }
/* A list-shaped control for list-shaped settings. Label goes to the top of the
   row rather than centring against five rows of textarea. */
.settings-row-wide { align-items: start; }
.settings-row-wide label { padding-top: 7px; }
.settings-row textarea {
  width: 100%; min-width: 0; resize: vertical;
  font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 12px;
  line-height: 1.6; padding: 7px 9px;
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: 8px;
}
.settings-row textarea:focus { outline: none; box-shadow: 0 0 0 3px rgba(17,16,19,0.10); }
/* Credential fields: the input keeps the row's full width, the clear button
   sits beside it. Placeholder carries the "saved, ending …1f3a" state, since
   the value itself never reaches the browser. */
.secret-line { display: flex; gap: 6px; align-items: center; }
.secret-line input { flex: 1 1 auto; min-width: 0; }
.secret-clear {
  flex: 0 0 auto; width: 26px; height: 26px; line-height: 1;
  border: 1px solid var(--line-strong); border-radius: 6px;
  background: var(--surface); color: var(--muted);
  font-family: inherit; font-size: 14px; cursor: pointer;
}
.secret-clear:hover:not(:disabled) { color: var(--danger); border-color: var(--danger); }
.secret-clear:disabled { opacity: .4; cursor: default; }
.ai-provider-stack { display: flex; flex-direction: column; gap: 8px; }
.ai-provider-stack input { margin-bottom: 0; }
.ai-provider-stack select { width: 100%; }
.ai-model-line { display: flex; gap: 8px; align-items: center; }
.ai-model-line select { flex: 1; }
.ai-model-fetch, .ai-model-update {
  flex: 0 0 auto;
  width: 34px; height: 34px;
  border: 1.5px solid var(--ink); border-radius: 8px;
  background: var(--surface); color: var(--ink);
  font-size: 18px; font-weight: 600; line-height: 1;
  cursor: pointer;
}
.ai-model-fetch:hover, .ai-model-update:hover { background: var(--surface-3); }
.settings-foot { display: flex; justify-content: space-between; align-items: center; margin-top: 16px; }
.settings-msg { font-size: 12px; color: var(--muted); max-width: 60%; }
.lab-templates-box { margin-top: 10px; }
.lab-templates-hint { font-size: 12px; color: var(--muted); margin-bottom: 10px; }
.lab-templates-upload { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.lab-templates-upload input[type="file"] { flex: 1; min-width: 200px; font-size: 13px; }
.lab-template-list { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.lab-template-item {
  display: flex; justify-content: space-between; align-items: center;
  background: var(--surface); border: 1.5px solid var(--ink); border-radius: 10px;
  padding: 8px 12px; font-size: 13px;
}
.lab-template-item span { font-weight: 600; color: var(--ink); }
.lab-template-item small { color: var(--muted); font-size: 11px; }
.token-new { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.token-new input[type=text] { flex: 1 1 160px; }
.token-new input[type=number] { width: 80px; }
.token-new input[type=text], .token-new input[type=number] {
  padding: 7px 9px; border: 1px solid var(--line); border-radius: 6px;
  font-size: 12px; background: var(--bg); color: var(--ink);
}
.token-new label { font-size: 11px; color: var(--muted); display: flex; align-items: center; gap: 3px; }
.token-reveal {
  margin-top: 10px; padding: 10px; border: 1px solid var(--warn);
  border-radius: 6px; background: color-mix(in srgb, var(--warn) 10%, transparent);
}
.token-reveal code {
  display: block; margin: 6px 0; padding: 8px; background: var(--bg);
  border-radius: 4px; font-size: 12px; word-break: break-all; user-select: all;
}
.token-reveal small { color: var(--muted); font-size: 11px; }
.token-revoked { opacity: .45; text-decoration: line-through; }
.token-health { display: block; margin-top: 3px; }
.export-row { display: flex; align-items: center; justify-content: flex-end; gap: 8px;
              margin: 4px 0 20px; }
.view-select { width: 100%; background: var(--surface); border: 1px solid var(--line);
               color: var(--body); border-radius: 8px; padding: 5px 7px;
               font: inherit; font-size: 11px; cursor: pointer; }
.view-actions { display: flex; gap: 4px; margin-top: 5px; }
.view-btn { flex: 1; background: var(--surface); border: 1px solid var(--line);
            color: var(--body); border-radius: 7px; padding: 4px 0; font: inherit;
            font-size: 10px; cursor: pointer; }
.view-btn:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }
.view-btn:disabled { opacity: .35; cursor: default; }
.export-hint { font-size: 11px; color: var(--muted); margin-right: auto; }
.export-btn { font-size: 12px; padding: 5px 12px; }
/* The only colour in a monochrome panel, so a degraded consumer is the one
   thing that draws the eye. Literal values, not palette variables: the palette
   is deliberately monochrome (--red is a pale grey), so going through it would
   render all three states identical.
   Two class selectors, because `.lab-template-item span` is more specific than
   a bare `.health-bad` and silently won — the states rendered in plain grey. */
.token-health .health-ok   { color: #6fbf8b; }
.token-health .health-warn { color: #d6b25e; }
.token-health .health-bad  { color: #e0736a; font-weight: 600; }

.users-grid { display: flex; flex-direction: column; gap: 12px; margin-top: 10px; }
.users-form { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.users-form input, .users-form select { padding: 8px 10px; border: 1.5px solid var(--ink); border-radius: 8px; font-family: inherit; font-size: 13px; background: var(--surface); color: var(--body); }
.users-form input { min-width: 160px; flex: 1; }
.users-form select { min-width: 90px; }
.users-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.users-table th, .users-table td { padding: 8px 10px; text-align: left; border-bottom: 1px solid var(--line); }
.users-table th { font-weight: 600; color: var(--ink); }
.users-table td { color: var(--body); }
.users-table .role-badge { display: inline-block; padding: 2px 8px; border-radius: 100px; font-size: 11px; font-weight: 600; background: var(--line); color: var(--ink); }
.users-table .role-badge.admin { background: var(--surface-3); }
.users-actions { display: flex; gap: 6px; }
.users-actions button { padding: 4px 10px; border: 1px solid var(--ink); border-radius: 6px; background: var(--surface); font-size: 12px; cursor: pointer; }
.users-actions button:hover { background: var(--surface-3); }
.users-actions button.danger { background: var(--neutral-1); color: #fff; border-color: var(--neutral-1); }
.users-actions button.danger:hover { background: #c41a0b; }

.vcard-id {
  display: inline-block; align-self: flex-start;
  font-family: 'JetBrains Mono', monospace; font-size: 12px; font-weight: 600;
  color: var(--ink); background: var(--line);
  padding: 3px 10px; border-radius: 8px; letter-spacing: .2px;
}
.vcard-title { font-size: 15px; font-weight: 700; color: var(--ink); line-height: 1.4; letter-spacing: -.005em; }
.vcard-summary { font-size: 13px; color: var(--muted); line-height: 1.55; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.vcard-llm {
  display: inline-flex; align-items: baseline; gap: 6px; max-width: 100%;
  font-size: 12px; color: var(--body); line-height: 1.4;
  border-left: 2px solid var(--neutral-3); padding-left: 8px; cursor: help;
}
.vcard-llm .llm-prefix {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 700;
  color: var(--neutral-3); letter-spacing: .5px;
}
.vcard-link { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.vcard-link a { color: var(--ink); font-weight: 500; border-bottom: 1px solid var(--ink); }
.vcard-link a:hover { background: var(--surface-3); text-decoration: none; }

/* ── Pagination ── */
.pagination-row { max-width: 1260px; margin: 8px auto 32px; padding: 0 40px; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px; }
.page-btn {
  font-family: inherit; font-size: 13px; font-weight: 700; color: var(--ink);
  background: var(--surface); border: 1.5px solid var(--ink); border-radius: 8px;
  min-width: 34px; height: 34px; padding: 0 10px; cursor: pointer;
  transition: all .2s var(--spring);
}
.page-btn:hover:not(:disabled) { background: var(--surface-3); transform: translateY(-2px); box-shadow: var(--shadow-soft); }
.page-btn.active { background: var(--ink); color: var(--surface); }
.page-btn:disabled { opacity: .5; cursor: default; }
.page-ellipsis { color: var(--muted); padding: 0 4px; }
.page-info { width: 100%; text-align: center; font-size: 12px; color: var(--muted); margin-bottom: 4px; }

/* ── Skip link ── */
.skip-link {
  position: absolute; top: -50px; left: 8px;
  background: var(--ink); color: var(--surface-3);
  padding: 8px 18px; border-radius: var(--pill);
  font-weight: 700; font-size: 13px; z-index: 100;
  text-decoration: none; transition: top .15s;
}
.skip-link:focus { top: 8px; outline: 2px solid var(--surface-3); outline-offset: 2px; }

/* ── Empty / Loading ── */
.empty { grid-column: 1/-1; text-align: center; padding: 80px 20px; color: var(--muted); }
.loading { grid-column: 1/-1; text-align: center; padding: 60px; color: var(--muted); }
.spinner { display: inline-block; width: 28px; height: 28px; border: 3px solid var(--line); border-top-color: var(--neutral-2); border-radius: 50%; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Footer ── */
.footer { max-width: 1260px; margin: 0 auto; padding: 24px 40px; text-align: center; color: var(--muted); font-size: 12px; border-top: 1px solid var(--ink); }

@media (max-width: 860px) {
  .nav-inner { display: flex; flex-direction: column; align-items: stretch; height: auto; padding: 12px 0; gap: 10px; }
  .nav-search { max-width: none; width: 100%; }
  .nav-right { align-self: flex-end; }
  .grid { grid-template-columns: 1fr; }
  /* .hero / .hero-title dropped: the oversized display heading they styled was
     removed from the markup when the sidebar shell landed. */
  .meta-row, .filter-row, .grid, .footer { padding-left: 16px; padding-right: 16px; }
  .nav { padding: 0 16px; }
  .settings-row { grid-template-columns: 1fr; }
  .settings-row label { text-align: left; }
}

/* ══ Shell ════════════════════════════════════════════════════════════════
   Sidebar + content, replacing the old full-width top bar. The sidebar owns
   identity, navigation and the stats that used to float in a fixed card. */
.app { display: grid; grid-template-columns: 248px 1fr; min-height: 100vh; }
.sidebar {
  background: var(--surface); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 4px; padding: 14px 10px;
  position: sticky; top: 0; height: 100vh; overflow-y: auto;
}
.sb-brand {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 10px 14px; font-size: 13px; font-weight: 600;
  color: var(--ink); letter-spacing: -.01em;
}
.sb-brand .dot { width: 16px; height: 16px; border-radius: 5px; background: var(--ink); flex: 0 0 auto; }
.sb-item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border-radius: 8px; border: 1px solid transparent;
  background: transparent; color: var(--body);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.sb-item:hover { background: var(--surface-2); color: var(--ink); }
.sb-item.active { background: var(--surface-3); border-color: var(--line); color: var(--ink); font-weight: 500; }
.sb-item .ico { width: 15px; text-align: center; opacity: .75; flex: 0 0 auto; }
a.sb-item { text-decoration: none; }
a.sb-new { border-color: var(--line); color: var(--ink); font-weight: 600; margin-top: 4px; }
a.sb-new:hover { background: var(--surface-3); text-decoration: none; }
a.sb-new .ico { opacity: 1; }
.sb-sep { height: 1px; background: var(--line); margin: 10px 6px; }
.sb-label {
  font-size: 10px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--muted); font-weight: 600; padding: 4px 10px;
}
.sb-scroll { overflow-y: auto; min-height: 0; }
.sb-foot { margin-top: auto; padding-top: 8px; }
.sb-user-wrap { position: relative; }
.sb-user { display: flex; align-items: center; gap: 9px; width: 100%; margin-top: 4px;
  padding: 8px 10px; border-radius: 8px; border: 1px solid transparent;
  background: transparent; color: var(--body); font: inherit; text-align: left; cursor: pointer; }
.sb-user:hover { background: var(--surface-2); }
.sb-user-avatar { width: 26px; height: 26px; flex: 0 0 auto; border-radius: 50%;
  background: var(--surface-3); color: var(--ink); font-weight: 700; font-size: 13px;
  display: flex; align-items: center; justify-content: center; }
.sb-user-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; flex: 1 1 auto; }
.sb-user-name { font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-user-role { font-size: 11px; color: var(--muted); }
.sb-user-caret { color: var(--muted); font-size: 11px; flex: 0 0 auto; transition: transform .15s; }
.sb-user[aria-expanded="true"] .sb-user-caret { transform: rotate(180deg); }
/* Account actions pop above the user chip (they precede it in the DOM column). */
.sb-user-menu { display: none; flex-direction: column; gap: 2px; margin-bottom: 4px; }
.sb-user-menu.open { display: flex; }

.content { min-width: 0; display: flex; flex-direction: column; }
.page-head {
  display: flex; align-items: center; gap: 14px;
  padding: 20px 28px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--bg); z-index: 20;
}
.page-title { font-size: 17px; font-weight: 600; color: var(--ink); letter-spacing: -.01em; white-space: nowrap; }
.page-head .nav-search { max-width: 420px; margin-left: auto; }
.page-actions { display: flex; align-items: center; gap: 10px; }

/* ══ Controls ═══════════════════════════════════════════════════════════ */
.nav-search input {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 9px; padding: 8px 14px 8px 34px; font-size: 13px; color: var(--ink);
}
.nav-search input:focus { border-color: var(--line-strong); box-shadow: none; }
.nav-search::before { left: 12px; font-size: 12px; opacity: .45; }

.cat-pill {
  padding: 5px 13px; border-radius: var(--pill); border: 1px solid var(--line);
  background: var(--surface); font-size: 12px; font-weight: 500; color: var(--body);
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.cat-pill:hover { background: var(--surface-2); color: var(--ink); transform: none; box-shadow: none; }
.cat-pill.active { background: var(--ink); color: var(--bg); border-color: var(--ink); font-weight: 600; }
.cat-pill:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.filter-row { max-width: none; padding: 0 28px; margin-top: 8px; }
#timeRow { margin-top: 18px; }

.theme-btn, .user-btn {
  background: var(--surface); border: 1px solid var(--line); color: var(--body);
  box-shadow: none; border-radius: 8px;
}
.theme-btn { width: 32px; height: 32px; font-size: 13px; }
.theme-btn:hover, .user-btn:hover { background: var(--surface-2); color: var(--ink); transform: none; }
.user-dropdown {
  background: var(--surface); border: 1px solid var(--line); box-shadow: 0 10px 30px rgba(0,0,0,.5);
  border-radius: 10px;
}
.user-dropdown-item { color: var(--body); font-size: 13px; }
.user-dropdown-item:hover { background: var(--surface-2); color: var(--ink); }
.user-dropdown-hr { border-top: 1px solid var(--line); }
.pushed-toggle { color: var(--body); font-weight: 500; }
.pushed-toggle .switch { background: var(--surface-3); border: 1px solid var(--line); }
.pushed-toggle .switch::after { background: var(--muted); }
.pushed-toggle input:checked + .switch { background: var(--ok); }
.pushed-toggle input:checked + .switch::after { background: #fff; }

/* ══ Cards ══════════════════════════════════════════════════════════════ */
.grid { max-width: none; padding: 18px 28px 60px; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); }
.vcard {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px; gap: 9px;
  transition: background .15s ease, border-color .15s ease;
}
.vcard:hover { transform: none; box-shadow: none; background: var(--surface-2); border-color: var(--line-strong); }
.vcard-date { color: var(--muted); font-size: 11px; }

/* Neutral chrome for the non-severity badges. Severity is the one dimension
   allowed a hue, and it lives in its own block at the end of the file. */
.src-badge, .reason-badge, .pr-badge, .nm-cve {
  border-radius: 6px; border: 1px solid var(--line); background: var(--surface-2);
  color: var(--body); font-weight: 500; letter-spacing: 0; text-transform: none;
}
.pr-badge { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.exploit-badge.none { background: transparent; color: var(--muted); border-color: var(--line); }
.tag-chip { border: 1px solid var(--line); background: var(--surface-2); color: var(--body); }
.tag-chip.on { background: var(--ink); color: var(--bg); border-color: var(--ink); }
/* Push state is encoded by SHAPE first, hue second: filled = sent, ring =
   selected but not yet delivered, bar = filtered out. Two reasons. It has to
   survive a colour-blind reader, and — the reason it changed — these were the
   only saturated pixels on the page, so an 8px delivery-status dot was
   out-shouting the severity badge next to it. Muted here, loud there. */
.pushed-dot { box-sizing: border-box; box-shadow: none; }
.pushed-dot.sent     { background: var(--ok); border: 1px solid var(--ok); }
.pushed-dot.selected { background: transparent; border: 2px solid var(--warn); }
.pushed-dot.no       { background: var(--muted); border-radius: 1px; height: 2px; align-self: center; }
/* Source health: online=green, offline=grey, unused(no data)=white+ring. */
.src-dot             { background: #f4f4f5; border: 1px solid var(--line-strong); box-shadow: none; }
.src-dot.ok          { background: var(--ok); border-color: var(--ok); box-shadow: none; }
.src-dot.bad         { background: var(--muted); border-color: transparent; box-shadow: none; animation: none; }

/* ══ Modals ═════════════════════════════════════════════════════════════ */
.modal { background: rgba(0,0,0,.66); backdrop-filter: blur(2px); }
.nm-card, .login-card {
  background: var(--surface); border: 1px solid var(--line-strong);
  border-radius: 16px; box-shadow: 0 24px 70px rgba(0,0,0,.6); padding: 22px 24px;
}
.nm-sep { border: none; border-top: 1px solid var(--line); }
.nm-btn {
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; box-shadow: none; font-weight: 500;
}
.nm-btn:hover { background: var(--surface-3); transform: none; box-shadow: none; }
.nm-btn.nm-primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
.nm-btn.nm-primary:hover { opacity: .88; background: var(--ink); }

/* ══ Settings: one tab per group ════════════════════════════════════════ */
#settingsModal .nm-card { max-width: 820px; }
#settingsModal .nm-title { font-size: 16px; font-weight: 600; color: var(--ink); margin-bottom: 2px; }
.set-tabs {
  display: flex; gap: 22px; border-bottom: 1px solid var(--line);
  margin: 10px 0 18px; overflow-x: auto; scrollbar-width: thin;
}
.set-tabs::-webkit-scrollbar { height: 3px; }
.set-tabs::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 2px; }
.set-tab {
  background: none; border: none; border-bottom: 2px solid transparent;
  padding: 0 0 10px; margin-bottom: -1px; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--muted); white-space: nowrap;
}
.set-tab:hover { color: var(--body); }
.set-tab.active { color: var(--ink); border-bottom-color: var(--ink); font-weight: 500; }
.set-panel { display: none; }
.set-panel.active { display: block; }
.settings-row input, .settings-row select, .token-new input {
  background: var(--surface-2); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; padding: 8px 10px;
}
.settings-row input:focus, .settings-row select:focus { box-shadow: none; border-color: var(--line-strong); }
.settings-group-title { border-bottom: none; color: var(--muted); font-size: 11px;
  text-transform: uppercase; letter-spacing: .08em; margin: 0 0 12px; }
.settings-foot { border-top: 1px solid var(--line); padding-top: 14px; }
.lab-template-item, .token-reveal { background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px; }
.token-reveal code { background: var(--bg); border: 1px solid var(--line); }

@media (max-width: 860px) {
  .app { grid-template-columns: 1fr; }
  /* The sidebar turns into a row here, but .sb-item is width:100%, so every
     nav entry still claimed its own line: brand, list, new, analytics,
     settings, users and the account block ate the first 640px of an 844px
     phone. You had to scroll past a screen of navigation to reach the first
     vulnerability. Compact chips that wrap instead. */
  .sidebar { position: static; height: auto; flex-direction: row; flex-wrap: wrap;
             align-items: center; gap: 6px; padding: 10px 16px; }
  .sb-scroll, .sb-sep, .sb-label { display: none; }
  /* The site name is ~340 of 390 available px, which pushed every chip after it
     onto a line of its own. The mark alone identifies the app; the full name is
     already in the document title and the tab. */
  .sb-brand { padding: 0; flex: 0 0 auto; font-size: 0; gap: 0; }
  .sb-brand .dot { width: 20px; height: 20px; }
  .sb-item, a.sb-item, .sb-user {
    width: auto; flex: 0 0 auto; margin-top: 0;
    padding: 6px 10px; font-size: 12px; gap: 6px;
  }
  /* .sb-foot is a block whose children stack: that is what put the three icon
     buttons in a vertical column down the right-hand side. It becomes the
     right-hand group of the same row here. */
  .sb-foot { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center;
             gap: 6px; margin: 0 0 0 auto; padding-top: 0; }
  .sb-user-wrap { flex: 0 0 auto; }
  .sb-user-avatar { width: 22px; height: 22px; font-size: 11px; }
  .sb-user-role { display: none; }          /* "管理员" under the name: not worth a line here */
  /* Icon-only for the secondary destinations; the labels are what made these
     wrap onto three rows, and the icons are unambiguous next to each other. */
  #analyticsItem span:not(.ico), #settingsItem span:not(.ico), #usersItem span:not(.ico) { display: none; }
  #analyticsItem, #settingsItem, #usersItem {
    width: 34px; justify-content: center; padding: 6px 0;
    border: 1px solid var(--line);
  }
  /* Density is a desktop concern: mobile rows are two-line regardless. */
  #densityGroup { display: none; }
  /* .page-head already wrapped, but its two children did not: the search box
     keeps a min-width and .page-actions is a nowrap flex row, so title +
     search + filters + pushed + theme + language sat on one 566px line inside
     a 375px viewport and pushed the whole document sideways. Search takes its
     own row; the controls wrap. */
  .page-head { flex-wrap: wrap; padding: 14px 16px; row-gap: 10px; }
  .page-head .nav-search { order: 3; flex: 1 0 100%; max-width: none; margin-left: 0; }
  .page-head .nav-search input { width: 100%; }
  .page-actions { flex-wrap: wrap; margin-left: auto; gap: 8px; }
  .page-title { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .filter-row, .grid { padding-left: 16px; padding-right: 16px; }
  .toolbar { padding-left: 16px; padding-right: 16px; gap: 12px; }
  .meta-row { padding-left: 16px; padding-right: 16px; }
}

/* Header stats: a quiet strip, not a hero. */
.meta-row { max-width: none; padding: 16px 28px 4px; margin: 0; justify-content: flex-start; }
.stats { gap: 0; }
.stat-item { flex-direction: column; align-items: flex-start; gap: 2px; padding: 0 20px 0 0; }
.stat-item + .stat-item { padding-left: 20px; border-left: 1px solid var(--line); }
.stat-num { font-family: inherit; font-size: 18px; font-weight: 600; color: var(--ink); letter-spacing: -.01em; }
.stat-label { font-size: 10px; letter-spacing: .07em; color: var(--muted); font-weight: 500; }

/* Sidebar stat rows */
.sb-scroll h4 { font-size: 10px; font-weight: 600; line-height: 1; text-transform: uppercase;
  letter-spacing: .09em; color: var(--muted); margin: 12px 10px 6px; }
.sb-scroll h4:first-child { margin-top: 2px; }
.side-row { display: flex; justify-content: space-between; align-items: center;
  padding: 4px 10px; font-size: 12px; border-radius: 6px; }
.side-row:hover { background: var(--surface-2); }
.side-row .lbl { color: var(--body); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-row .num { font-family: inherit; font-weight: 500; color: var(--ink); font-size: 11px; }

/* Cards: monospace only where it aids scanning (identifiers, scores). */
.vcard-cve, .nm-cve, .vcard-date, .sev-badge { font-family: 'JetBrains Mono', ui-monospace, monospace; }
.vcard h2, .vcard h3, .vcard-title { font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.4; }
.vcard p, .vcard-summary { font-size: 12.5px; color: var(--body); line-height: 1.55; }
.footer { color: var(--muted); font-size: 11px; padding: 24px 28px; border-top: 1px solid var(--line); }
.skip-link:focus { background: var(--surface); color: var(--ink); border: 1px solid var(--line-strong); }
.spinner { border-color: var(--line); border-top-color: var(--ink); }
.pagination-row { padding: 0 28px 40px; }

/* ══ Sidebar filters ═════════════════════════════════════════════════════
   248px is tight, so the pills shrink and wrap and each group gets a small
   caption instead of an inline label. */
/* Gated on min-width. Unconditional, these sat *after* the max-width: 860px
   block that collapses the shell to one column and silently beat it: the grid
   kept a 264px sidebar track on a 375px phone, leaving the content column 111px
   wide. Everything inside it — the header, the search box, the toolbar — then
   overflowed the viewport, which is what produced the horizontal scroll. */
@media (min-width: 861px) {
  .sidebar { width: 264px; }
  .app { grid-template-columns: 264px 1fr; }
}
#filterPane { flex: 1 1 auto; padding: 2px 2px 6px; }
.f-group { margin: 0 0 13px; }
.f-group:last-child { margin-bottom: 4px; }
.f-label {
  font-size: 9.5px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .1em; color: var(--muted); padding: 0 8px; margin-bottom: 6px;
}
#filterPane .filter-row { padding: 0 6px; margin: 0; gap: 4px; }
#filterPane .cat-pill {
  padding: 3px 9px; font-size: 11px; line-height: 1.45; border-radius: 6px;
}
#filterPane .cat-pill.active { font-weight: 600; }
#filterPane .src-dot { width: 5px; height: 5px; margin-right: 4px; }
/* Scrollbar stays out of the way until hovered. */
#filterPane::-webkit-scrollbar { width: 6px; }
#filterPane::-webkit-scrollbar-thumb { background: transparent; border-radius: 3px; }
#filterPane:hover::-webkit-scrollbar-thumb { background: var(--line-strong); }

/* ══ Severity ════════════════════════════════════════════════════════════
   The one place hue is allowed back: a four-step scale has to be readable at a
   glance across a wall of cards, and lightness alone was not carrying it.
   Kept desaturated so it reads as a signal, not decoration.

   THE single definition of .sev-badge. Everything the badge needs is declared
   here — do not reintroduce a copy earlier in the file. */
.sev-badge {
  display: inline-flex; align-items: center;
  padding: 2px 8px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--surface-2); color: var(--body);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10px; font-weight: 600; letter-spacing: 0; text-transform: none;
  white-space: nowrap;
}
/* The per-theme hexes moved to css/tokens.css so both themes are defined in one
   place, next to the contrast notes that justify them. */
.sev-badge.sev-critical { background: var(--sev-crit-bg); color: var(--sev-crit-fg); border-color: var(--sev-crit-line); }
.sev-badge.sev-high     { background: var(--sev-high-bg); color: var(--sev-high-fg); border-color: var(--sev-high-line); }
.sev-badge.sev-medium   { background: var(--sev-med-bg);  color: var(--sev-med-fg);  border-color: var(--sev-med-line); }
.sev-badge.sev-low      { background: var(--sev-low-bg);  color: var(--sev-low-fg);  border-color: var(--sev-low-line); }

.ai-model-line { display: flex; gap: 6px; align-items: center; }
.ai-model-line select { flex: 1 1 auto; min-width: 0; }
.ai-model-line button {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 7px;
  background: var(--surface-2); border: 1px solid var(--line);
  color: var(--body); cursor: pointer; font-size: 13px; line-height: 1;
}
.ai-model-line button:hover { background: var(--surface-3); color: var(--ink); }
.settings-hint { grid-column: 1 / -1; font-size: 11px; color: var(--muted);
  font-family: 'JetBrains Mono', ui-monospace, monospace; padding-left: 190px; }

.api-endpoints { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 12px; }
.api-endpoints-title { font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .09em; color: var(--muted); margin-bottom: 8px; }
.api-endpoint { display: flex; align-items: center; gap: 8px; padding: 5px 0; font-size: 12px; }
.api-method { flex: 0 0 auto; font-size: 10px; font-weight: 700; color: var(--muted);
  border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; }
.api-url { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-family: 'JetBrains Mono', ui-monospace, monospace;
  color: var(--ink); background: var(--bg); border: 1px solid var(--line);
  border-radius: 6px; padding: 4px 8px; }
.api-scope { flex: 0 0 auto; font-size: 10.5px; color: var(--muted); }
.api-copy { flex: 0 0 auto; background: var(--surface-3); border: 1px solid var(--line);
  color: var(--body); border-radius: 6px; padding: 3px 9px; font-size: 11px; cursor: pointer; }
.api-copy:hover { color: var(--ink); }

/* Human-readable enrichment + machine-API controls.
   The raw ENRICH_* / VULNPILOT_* keys were text boxes the operator had to
   already understand; these render them as toggles, checkbox groups and a
   one-line explanation of what each filter actually gates. */
.settings-lead { grid-column: 1 / -1; font-size: 12px; color: var(--muted);
  line-height: 1.55; margin: 2px 0 6px; }
.settings-sub { grid-column: 1 / -1; font-size: 11px; font-weight: 700; color: var(--body);
  text-transform: uppercase; letter-spacing: .06em; margin: 14px 0 2px; }
.settings-sub:first-child { margin-top: 0; }
.check-group { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; }
.check-group label { display: inline-flex; align-items: center; gap: 5px; font-size: 12px;
  font-weight: 500; color: var(--body); cursor: pointer; }
.check-group input[type=checkbox] { width: auto; margin: 0; }
.enrich-dim { opacity: .45; pointer-events: none; }

/* Header language picker */
.lang-sel {
  background: var(--surface); border: 1px solid var(--line); color: var(--body);
  border-radius: 8px; padding: 6px 8px; font: inherit; font-size: 12px;
  height: 32px; cursor: pointer;
}
.lang-sel:hover { background: var(--surface-2); color: var(--ink); }
.user-btn { cursor: pointer; }

/* ── item 6: the settings modal keeps one size across tabs ──
   Panels differ wildly in height (Telegram has two fields, Machine API has
   four); without a floor the dialog jumped on every tab click. */
#settingsModal .nm-card { height: 620px; max-height: 86vh; display: flex; flex-direction: column; }
#settingsModal .nm-head, #settingsModal .nm-title,
#settingsModal .set-tabs, #settingsModal .settings-foot { flex: 0 0 auto; }
.set-body { flex: 1 1 auto; overflow-y: auto; min-height: 0; padding-right: 4px; }
.set-body::-webkit-scrollbar { width: 6px; }
.set-body::-webkit-scrollbar-thumb { background: var(--line-strong); border-radius: 3px; }

.empty-detail { font-size: 12px; color: var(--muted); margin: 6px 0 12px;
  font-family: 'JetBrains Mono', ui-monospace, monospace; word-break: break-word; }

@keyframes fadeUp { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:translateY(0); } }
/* "+n new" pill and the flash on freshly arrived rows. Both were indigo
   (#4f46e5 / #6366f1) — a third hue, on the two elements most likely to be on
   screen at the same time as a critical badge. Neutral now; the animation is
   what makes them noticeable, not the colour. */
.new-badge { display:block; width:fit-content; margin:0 auto 10px; padding:6px 16px;
  background:var(--ink); color:var(--bg); border-radius:999px; font-size:13px; font-weight:600;
  cursor:pointer; user-select:none;
  animation:newBadgeIn .3s ease both; }
.new-badge.hidden { display:none; }
@keyframes newBadgeIn { from { opacity:0; transform:translateY(-6px); } to { opacity:1; transform:translateY(0); } }
.vcard-new, .vrow-new { animation:vcardNewFlash 2.5s ease-out 1; }
@keyframes vcardNewFlash { from { outline:2px solid var(--neutral-3); } to { outline:2px solid transparent; } }
::selection { background: var(--surface-3); color: var(--ink); }

/* ══ Toolbar ═════════════════════════════════════════════════════════════
   Sort, layout and density: three controls that change how the current result
   set is drawn, not which rows it contains. Sort used to live in the 264px
   sidebar among the filters, which is where you look for "narrow this down",
   not for "show it differently". */
.toolbar {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  padding: 10px 28px 2px;
}
.tb-group { display: flex; align-items: center; gap: 8px; }
.tb-label {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .09em; color: var(--muted);
}
.tb-count {
  margin-left: auto; font-size: 11px; color: var(--muted);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
}

/* Segmented control = single choice. The sidebar's separate pills = multi
   choice. Every filter group on this page used to render as the same pill
   whether it was exclusive or additive, so there was no way to tell from the
   control whether clicking a second option would replace the first. */
.seg {
  display: inline-flex; background: var(--surface);
  border: 1px solid var(--line); border-radius: 7px; overflow: hidden;
}
.seg .cat-pill {
  border: none; border-radius: 0; padding: 4px 11px;
  font-size: 11px; font-weight: 500; background: transparent; color: var(--body);
}
.seg .cat-pill + .cat-pill { border-left: 1px solid var(--line); }
.seg .cat-pill:hover { background: var(--surface-2); color: var(--ink); transform: none; box-shadow: none; }
.seg .cat-pill.active { background: var(--surface-3); color: var(--ink); font-weight: 600; }
.seg .cat-pill:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

/* .vitem carries no styles on purpose. It is the "one record in the list"
   hook that .vrow and .vcard share, so scroll anchoring, the refresh diff and
   the tests can address a record without caring which layout is on screen.
   Do not delete it as unused. */

/* ══ Table view ══════════════════════════════════════════════════════════
   The default. A 380px card grid fits six records on a 1440p screen; this
   fits ~25. The list has four figures of records and the job is to scan them.
   Header and rows share --cols so the columns line up, and share cell classes
   so a column can be dropped from both at a breakpoint. */
.rows {
  --cols: 22px 88px 128px minmax(220px, 1fr) 104px 58px 62px 56px 22px 26px;
  --row-pad: 9px;
  display: block; padding: 8px 28px 60px;
}
.rows[data-density="compact"] { --row-pad: 5px; }
.rows[data-density="roomy"]   { --row-pad: 14px; }

.rows-head, .vrow {
  display: grid; grid-template-columns: var(--cols);
  align-items: center; gap: 12px;
}
.rows-head {
  position: sticky; top: 0; z-index: 3;
  padding: 4px 10px 6px; background: var(--bg);
  border-bottom: 1px solid var(--line-strong);
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .08em; color: var(--muted);
}
.vrow {
  position: relative; padding: var(--row-pad) 10px;
  border-bottom: 1px solid var(--line); border-radius: 6px; min-width: 0;
}
.vrow:hover { background: var(--surface-2); }
.vrow > span {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px;
}
.vr-cve  { font-family: 'JetBrains Mono', ui-monospace, monospace; font-size: 11px; color: var(--body); }
.vr-title { font-size: 13px; }
.vr-title a { color: var(--ink); font-weight: 500; }
.vr-src, .vr-access { color: var(--muted); font-size: 11px; }
.vr-epss, .vr-age {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px; color: var(--muted); text-align: right;
}
.vr-state, .vr-ext { overflow: visible; text-align: center; }
.vr-sev .sev-badge { width: 100%; justify-content: center; }
/* The cells line up exactly; the badge centres its text inside them, so a
   left-aligned header reads as a 20px misalignment that is not there. */
.rows-head .vr-sev { text-align: center; }
.epss-cell.epss-mid { color: var(--sev-high-line); }
.epss-cell.epss-hi  { color: var(--danger); font-weight: 700; }

/* Exploit availability. Ranked by weight, not by hue — the colour budget buys
   one exception, and `itw` is it: an entry there means someone is being
   attacked with this today, which is the same claim the critical badge makes. */
.vr-exp { text-align: center; overflow: visible; }
.exp-cell { font-size: 10px; line-height: 1; color: var(--muted); }
.exp-cell.exp-poc        { color: var(--muted); }
.exp-cell.exp-weaponized { color: var(--neutral-2); font-weight: 700; }
.exp-cell.exp-itw        { color: var(--sev-crit-line); font-weight: 700; }

/* Exploit code beat the advisory. The colour budget's other exception: this is
   a severity claim about the disclosure itself, not a category label. */
.poc-first {
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  color: var(--sev-high-fg); background: var(--sev-high-bg);
  border-radius: 3px; padding: 1px 4px; vertical-align: 1px; white-space: nowrap;
}

/* Anything from the last 24h reads at full contrast; everything older is muted.
   "New since I last looked" becomes visible without reading a single number. */
.is-fresh { color: var(--ink); font-weight: 600; }

/* ══ Stretched link ══════════════════════════════════════════════════════
   One real <a> per row/card, blown up to cover the box. Replaces a div with
   role="link" that wrapped a second <a> — invalid ARIA, and it cost
   middle-click and ctrl-click, which is how you open things from a list you
   are working through. */
.vrow, .vcard { position: relative; }
.item-link { text-decoration: none; }
.item-link::after { content: ''; position: absolute; inset: 0; z-index: 1; }
.item-link:hover { text-decoration: underline; }
.item-ext { position: relative; z-index: 2; color: var(--muted); font-size: 13px; line-height: 1; }
.item-ext:hover { color: var(--ink); text-decoration: none; }
.vrow:has(.item-link:focus-visible),
.vcard:has(.item-link:focus-visible) { outline: 2px solid var(--focus); outline-offset: -2px; }
.item-link:focus-visible { outline: none; }

/* ══ Card view (slimmed) ═════════════════════════════════════════════════ */
.vcard-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 2px;
}
.vcard-src {
  font-size: 11px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.vcard-title { margin: 0; }
.vcard-title a { color: inherit; }

/* ══ Empty state ═════════════════════════════════════════════════════════ */
.empty-title { font-size: 14px; color: var(--body); }
.empty-actions {
  display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; margin-top: 14px;
}

/* ══ Table responsive ════════════════════════════════════════════════════
   Columns drop in reverse order of how often they decide anything: source and
   access go before EPSS, EPSS before the age. */
@media (max-width: 1240px) {
  .rows { --cols: 22px 88px 128px minmax(200px, 1fr) 58px 30px 62px 56px 22px 26px; }
  .vr-src { display: none; }
}
@media (max-width: 1040px) {
  .rows { --cols: 22px 84px 120px minmax(180px, 1fr) 30px 62px 56px 22px 26px; }
  .vr-epss { display: none; }
}
@media (max-width: 860px) {
  /* No horizontal scrolling: two lines per record instead. */
  .rows { padding-left: 16px; padding-right: 16px; }
  .rows-head { display: none; }
  .vrow {
    grid-template-columns: auto auto 1fr auto; row-gap: 3px;
    grid-template-areas: "sev cve . age" "title title title title";
  }
  .vr-sev { grid-area: sev; } .vr-cve { grid-area: cve; } .vr-age { grid-area: age; }
  .vr-title { grid-area: title; white-space: normal; }
  .vr-access, .vr-state, .vr-ext, .vr-exp { display: none; }
}

/* ══ Filter pane: control shape carries selection semantics ══════════════
   Every group used to render as the same pill whether it was exclusive,
   additive, or a pair of independent switches, so nothing on screen told you
   whether clicking a second option would replace the first. Three shapes now:

     segmented (joined, no gaps)  single choice — time
     pill + checkbox              additive      — category / asset / severity / source
     switch                       independent   — unauth, no-interaction

   The per-group "All" is really "clear this group", so it keeps no checkbox. */
.f-head {
  display: flex; align-items: center; gap: 8px;
  padding: 2px 8px 8px; margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}
.f-head-label {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .1em; color: var(--body);
}
.f-head-count {
  font-size: 10px; color: var(--muted);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
}
.f-reset {
  margin-left: auto; background: transparent; border: none; padding: 2px 4px;
  font: inherit; font-size: 11px; color: var(--muted); cursor: pointer;
}
.f-reset:hover:not(:disabled) { color: var(--ink); text-decoration: underline; }
.f-reset:disabled { opacity: .35; cursor: default; }
.f-reset:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

#filterPane .filter-row.seg { display: inline-flex; padding: 0; margin: 0 6px; gap: 0; }
#filterPane .filter-row.seg .cat-pill { border-radius: 0; }

#filterPane .multi .cat-pill { display: inline-flex; align-items: center; }
#filterPane .multi .cat-pill::before {
  content: ''; flex: 0 0 auto;
  width: 9px; height: 9px; margin-right: 5px;
  border: 1px solid var(--line-strong); border-radius: 2px;
  display: inline-block; vertical-align: -1px;
}
#filterPane .multi .cat-pill.active::before {
  background: var(--ink); border-color: var(--ink);
  /* Checkmark drawn with a clip-path so it needs no font, no icon set and no
     extra element inside a button that is already tight. */
  clip-path: polygon(14% 44%, 0 58%, 38% 100%, 100% 22%, 86% 8%, 38% 72%);
}
#filterPane .cat-pill.f-all::before { display: none; }
#filterPane .cat-pill.f-all {
  border: 1px dashed var(--line-strong); background: transparent; color: var(--muted);
}
#filterPane .cat-pill.f-all.active { background: var(--surface-3); color: var(--ink); border-style: solid; }

/* Independent switches: nothing about them should read as "one of these". */
#filterPane .toggles { flex-direction: column; align-items: stretch; gap: 2px; }
#filterPane .toggles .cat-pill {
  display: flex; align-items: center; justify-content: space-between;
  background: transparent; border-color: transparent; text-align: left;
}
#filterPane .toggles .cat-pill:not(.f-all)::after {
  content: ''; flex: 0 0 auto; width: 22px; height: 12px; margin-left: 8px;
  border-radius: 999px; background: var(--surface-3);
  border: 1px solid var(--line-strong);
  box-shadow: inset 6px 0 0 -3px var(--muted);   /* the knob, left */
}
#filterPane .toggles .cat-pill.active { background: transparent; color: var(--ink); }
#filterPane .toggles .cat-pill.active::after {
  background: var(--ok); border-color: var(--ok);
  box-shadow: inset -6px 0 0 -3px #fff;          /* knob slides right */
}
#filterPane .toggles .cat-pill:hover { background: var(--surface-2); }

/* Source list: search + busiest-first + collapsed tail. */
.f-search {
  width: calc(100% - 12px); margin: 0 6px 6px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 6px;
  padding: 4px 8px; font: inherit; font-size: 11px; color: var(--ink);
}
.f-search::placeholder { color: var(--muted); }
.f-search:focus-visible { outline: 2px solid var(--focus); outline-offset: -1px; }
.src-n {
  margin-left: 6px; font-size: 9.5px; color: var(--muted);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
}
.cat-pill.active .src-n { color: inherit; opacity: .75; }
/* Same [hidden] trap as .detail-nav: an author display wins over the UA rule. */
.f-more[hidden] { display: none; }
.f-more {
  display: block; margin: 4px 6px 0; padding: 2px 4px;
  background: transparent; border: none; font: inherit; font-size: 11px;
  color: var(--muted); cursor: pointer;
}
.f-more:hover { color: var(--ink); text-decoration: underline; }
.f-more:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ══ Mobile filter sheet ═════════════════════════════════════════════════
   The narrow-screen rule below used to be `.sb-scroll { display: none }` and
   nothing else, so a phone lost every filter the product has — time, sort,
   category, asset type, severity, access and source — leaving only search.
   Same pane, raised as a bottom sheet. */
.filter-toggle { display: none; }
.filter-backdrop { display: none; }

@media (max-width: 860px) {
  .filter-toggle {
    display: inline-flex; align-items: center; gap: 6px;
    background: var(--surface); border: 1px solid var(--line-strong);
    border-radius: var(--pill); padding: 6px 14px;
    font: inherit; font-size: 12px; font-weight: 600; color: var(--ink);
    cursor: pointer; min-height: 34px;
  }
  .filter-toggle:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
  .ft-count:not(:empty) {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
    background: var(--ink); color: var(--bg); font-size: 10px; line-height: 1;
  }

  body.filters-open { overflow: hidden; }
  .filter-backdrop[hidden] { display: none; }
  .filter-backdrop { display: block; position: fixed; inset: 0; z-index: 1100;
    background: rgba(0,0,0,.55); }

  body.filters-open .sb-scroll {
    display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 1200;
    max-height: 80vh; overflow-y: auto;
    background: var(--surface); border-top: 1px solid var(--line-strong);
    border-radius: 16px 16px 0 0; padding: 14px 8px calc(20px + env(safe-area-inset-bottom));
  }
  /* Grab handle, so the sheet reads as dismissable. */
  body.filters-open .sb-scroll::before {
    content: ''; display: block; width: 36px; height: 4px; margin: 0 auto 12px;
    border-radius: 2px; background: var(--line-strong);
  }
  body.filters-open .sb-scroll:focus { outline: none; }

  /* Touch targets. 11px pills at 3px padding are a desktop density. */
  body.filters-open #filterPane .cat-pill { padding: 8px 12px; font-size: 13px; }
  body.filters-open #filterPane .f-search { font-size: 14px; padding: 8px 10px; }
  body.filters-open .f-reset, body.filters-open .f-more { font-size: 13px; padding: 8px 6px; }
}

/* ══ Keyboard shortcuts dialog ═══════════════════════════════════════════ */
.sc-card { width: 460px; max-width: 92vw; }
.sc-list { margin-top: 6px; }
.sc-row {
  display: flex; align-items: baseline; gap: 14px;
  padding: 7px 0; border-bottom: 1px solid var(--line);
}
.sc-row:last-child { border-bottom: none; }
.sc-row dt { flex: 0 0 110px; display: flex; gap: 5px; flex-wrap: wrap; }
.sc-row dd { flex: 1 1 auto; font-size: 12.5px; color: var(--body); }
kbd {
  display: inline-block; min-width: 20px; padding: 2px 6px; text-align: center;
  background: var(--surface-2); border: 1px solid var(--line-strong);
  border-bottom-width: 2px; border-radius: 5px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px; color: var(--ink); line-height: 1.4;
}

/* ══ Loading ═════════════════════════════════════════════════════════════
   Skeletons on the first paint, a dim on reload. The spinner collapsed the
   container to nothing, so every result set landing shoved the page down. */
.skel {
  display: block; height: 11px; border-radius: 4px;
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: skelShimmer 1.4s ease-in-out infinite;
}
@keyframes skelShimmer { from { background-position: 100% 0; } to { background-position: 0 0; } }
.skel-row { pointer-events: none; }
.skel-row .vr-sev .skel  { width: 100%; height: 15px; }
.skel-row .vr-cve .skel  { width: 88%; }
.skel-row .vr-title .skel { width: 72%; }
.skel-row .vr-src .skel  { width: 70%; }
.skel-row .vr-epss .skel { width: 60%; margin-left: auto; }
.skel-row .vr-access .skel { width: 76%; }
.skel-row .vr-age .skel  { width: 64%; margin-left: auto; }
.skel-badge { width: 96px; height: 16px; }
.skel-line { height: 12px; }
/* Every skeleton row is the same width, which reads as a loading bar rather
   than as content. Staggering the title widths breaks that up. */
.skel-row:nth-child(3n)   .vr-title .skel { width: 58%; }
.skel-row:nth-child(3n+1) .vr-title .skel { width: 86%; }
.vcard.skel-row:nth-child(even) .skel-line { opacity: .8; }

/* Reload: keep the rows, take away the affordance.
   The container dims, not the rows. Each row carries an inline
   `animation: fadeUp ... both`, and a value produced by an animation outranks a
   normal declaration in the cascade — so with fill-mode `both` holding
   opacity:1 on every row, dimming .vitem did precisely nothing. Dimming the
   container is also one composited layer instead of a repaint per row. */
#cardList.is-loading { opacity: .45; transition: opacity .12s ease; }
#cardList.is-loading .vitem { pointer-events: none; }

/* ══ Focus ═══════════════════════════════════════════════════════════════
   Catch-all, so a control added later is visible to a keyboard by default
   instead of by remembering. :where() contributes no specificity, so every
   rule above still wins where it wants something different.

   --focus is a hue that appears nowhere else in the UI, deliberately: a ring
   drawn in --ink is easy to read as "selected" next to a pill that indicates
   selection the same way. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
/* Never the bare UA ring on a mouse click. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus:not(:focus-visible) {
  outline: none;
}

/* ══ Triage ══════════════════════════════════════════════════════════════
   Drawn as a treatment of the row rather than a tenth column: "tracking /
   done / ignored" reads faster as an accent, a dim and a strikethrough than as
   another word to parse across a wall of rows, and the columns are already at
   their width budget.

   No new hue. Tracking borrows --warn, the same token the "selected for push"
   dot uses, because both mean "open, waiting on something". */
.vrow.tri-tracking { box-shadow: inset 2px 0 0 var(--warn); }
.vcard.tri-tracking { border-left: 2px solid var(--warn); }
.tri-done    { opacity: .58; }
.tri-ignored { opacity: .42; }
.tri-ignored .vr-title a, .tri-ignored .vcard-title a { text-decoration: line-through; }
/* Hovering or focusing a dimmed row brings it back: a triaged record still has
   to be readable when you go looking for what you decided. */
.tri-done:hover, .tri-ignored:hover,
.tri-done:focus-within, .tri-ignored:focus-within { opacity: 1; }

/* ── Selection ── */
.vr-sel { display: flex; align-items: center; justify-content: center; overflow: visible; }
.sel-box {
  width: 13px; height: 13px; margin: 0; cursor: pointer;
  accent-color: var(--focus); flex: 0 0 auto;
  opacity: 0; transition: opacity .12s ease;
}
/* Out of the way until wanted, present the moment it is: on hover, when it is
   checked, and for the whole list once a selection exists. */
.vitem:hover .sel-box, .sel-box:checked, .sel-box:focus-visible,
#cardList.selecting .sel-box { opacity: 1; }
.vcard .sel-box { position: relative; z-index: 2; }
.vitem.is-selected { background: color-mix(in srgb, var(--focus) 12%, transparent); }
/* The tint lifts the background just enough to push --muted under AA — 4.48:1
   in dark, 4.19 in light, and no practical tint level clears 4.5 in both. The
   answer is not a fainter selection but louder text: the row you are about to
   act on should be more legible, not less. --body measures 8.07 / 7.54 here. */
.vitem.is-selected .vr-src,
.vitem.is-selected .vr-access,
.vitem.is-selected .vr-exp,
.vitem.is-selected .vr-epss,
.vitem.is-selected .vr-age,
.vitem.is-selected .vr-cve,
.vitem.is-selected .vcard-src { color: var(--body); }
.vrow.is-selected { box-shadow: inset 2px 0 0 var(--focus); }

/* ── Batch bar ── */
.batch-bar {
  position: sticky; top: 0; z-index: 12;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 28px 8px; padding: 8px 14px;
  background: var(--surface-3); border: 1px solid var(--line-strong); border-radius: 10px;
}
.batch-bar[hidden] { display: none; }
.bb-count {
  font-size: 12px; font-weight: 600; color: var(--ink);
  font-family: 'JetBrains Mono', ui-monospace, monospace; margin-right: 4px;
}
.batch-bar .nm-btn { padding: 4px 12px; font-size: 12px; }
.bb-sep { flex: 1 1 auto; }

@media (max-width: 860px) {
  .batch-bar { margin-left: 16px; margin-right: 16px; }
  /* The selection box keeps its column; everything else already collapsed. */
  .vrow { grid-template-areas: "sel sev cve age" "title title title title"; }
  .vr-sel { grid-area: sel; }
}

/* ══ Column chooser ══════════════════════════════════════════════════════
   Severity, CVE and title are not offered: a row without them is not a row.
   The rest are the same columns the responsive rules already drop at narrow
   widths, made a choice instead of a guess. */
.col-menu { position: relative; }
.col-btn { padding: 4px 11px; font-size: 11px; font-weight: 500; }
.col-pop {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  display: flex; flex-direction: column; gap: 2px; min-width: 148px; padding: 6px;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: 9px;
  box-shadow: 0 12px 34px rgba(0,0,0,.45);
}
.col-pop[hidden] { display: none; }
.col-opt {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; border-radius: 6px; font-size: 12px; color: var(--body); cursor: pointer;
}
.col-opt:hover { background: var(--surface-2); color: var(--ink); }
.col-opt input { accent-color: var(--focus); margin: 0; }

/* The track is dropped from --cols in JS; this drops the cells. Both are
   needed — without the track a hidden column leaves a gap, and without this
   the cells collapse into whatever track follows. */
.rows.hide-vr-src .vr-src,
.rows.hide-vr-epss .vr-epss,
.rows.hide-vr-exp .vr-exp,
.rows.hide-vr-access .vr-access,
.rows.hide-vr-age .vr-age,
.rows.hide-vr-state .vr-state { display: none; }

/* ══ Print ═══════════════════════════════════════════════════════════════
   A printed or PDF-exported list is a handover artefact: someone is taking it
   into a meeting or attaching it to a ticket.

   Rows become flex, not grid. The grid track list is set inline by the column
   chooser, so `display: none` on a cell in print left its track behind and
   shifted every following cell one column left — the severity badge ended up
   crushed into the 22px selection track. Flex needs no track list, so it is
   correct for any column configuration, including ones chosen after this rule
   was written. The header goes with it: without a track list it cannot line up,
   and a row that reads "CRIT 9.8 · CVE-… · title · source" does not need one. */
@media print {
  .sidebar, .page-head, .toolbar, .batch-bar, .pagination-row, .export-row,
  .new-badge, .filter-backdrop, .modal, .rows-head,
  .vr-sel, .vr-ext, .vr-state, .footer { display: none !important; }
  .app { display: block; }
  body { background: #fff !important; color: #000 !important; }

  /* The strip is worth keeping on a handover: totals and the triage figure are
     the summary someone reads first. */
  .meta-row { padding: 0 0 10px; border-bottom: 1px solid #000; margin-bottom: 10px; }
  .stat-num, .stat-label { color: #000 !important; }
  .stat-item + .stat-item { border-left-color: #999 !important; }

  .rows { padding: 0; }
  .vrow, .vcard {
    display: flex; align-items: baseline; gap: 10px;
    break-inside: avoid; background: none !important;
    border: none !important; border-bottom: 1px solid #ddd !important;
    padding: 5px 0; box-shadow: none !important;
  }
  .vcard { flex-direction: column; align-items: stretch; }
  /* On paper there is room to read the whole title, and no hover to reveal the
     rest of a truncated one. */
  .vr-title { flex: 1 1 auto; white-space: normal; overflow: visible; }
  .vr-title, .vr-cve, .vr-src, .vr-epss, .vr-access, .vr-age,
  .vcard-title, .vcard-summary, .vcard-src { color: #000 !important; }
  .vr-cve, .vr-src, .vr-access, .vr-age { flex: 0 0 auto; }
  a { color: #000 !important; text-decoration: none; }

  /* Triage dims a handled row on screen; on paper that reads as a bad printer,
     so the state is carried by the marks that survive greyscale. */
  .tri-done, .tri-ignored { opacity: 1; }
  .tri-ignored .vr-title a, .tri-ignored .vcard-title a { text-decoration: line-through; }
  .vrow.tri-tracking { box-shadow: none !important; border-left: 3px solid #666 !important; padding-left: 6px; }

  /* Severity fills do not survive greyscale either; the word and a box do. */
  .sev-badge {
    background: none !important; color: #000 !important;
    border: 1px solid #000 !important; flex: 0 0 auto;
  }
  .epss-cell { color: #000 !important; font-weight: 600; }
  @page { margin: 14mm; }
}
