/* AISLE Vulnerability Platform — design system
   Calm, editorial, document-forward. Cream canvas, sage accent, charcoal ink.
   Colour is used only to carry meaning (severity / status). Light + dark. */

:root {
  --canvas: #f6f3ec;
  --surface: #fbfaf6;
  --surface-2: #efeae0;
  --border: #e4dfd4;
  --border-strong: #d3cbba;
  --ink: #22231f;
  --ink-2: #4a4b44;
  --muted: #6b6b63;

  /* table rows: subtle neutral stripe / clearly distinct sage-washed hover */
  --row-alt: rgba(34, 35, 31, 0.035);
  --row-hover: rgba(94, 111, 88, 0.13);

  --sage: #5e6f58;
  --sage-strong: #4c5a47;
  --sage-tint: #dce3d6;
  --sage-ink: #ffffff;

  --accent: #b4552d; /* warm clay — destructive / critical emphasis */
  --accent-tint: #f2ddd1;

  --sev-critical: #a4133c; /* deep crimson (7.7:1 with white chip text) — the top of the ramp, hue-stepped off high's red */
  --sev-high: #d92818; /* bright vivid red (4.9:1 white) */
  --sev-medium: #f0a000; /* vivid amber; chips pair it with dark text (7.2:1) */
  --sev-low: #5e6f58;
  --sev-info: #77776e;
  --sev-unknown: #71716a; /* unassessed — muted, distinct from info; 4.9:1 with white chip text (WCAG AA) */

  --ok: #2f6b3e;   /* 5.0:1 on --ok-tint (AA at chip text size) */
  --warn: #8a4e14; /* 5.4:1 on --warn-tint (AA at chip text size) */
  --danger: #a23c22;
  --ok-tint: #dce8dd;
  --warn-tint: #f3e6d3;
  --danger-tint: #f2d9d2;

  --radius: 9px;
  --radius-sm: 6px;
  --shadow: 0 1px 2px rgba(34, 35, 31, 0.05), 0 4px 16px rgba(34, 35, 31, 0.04);
  --ring: 0 0 0 3px rgba(94, 111, 88, 0.28);

  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial,
    "Inter", sans-serif;
  --mono: "SF Mono", "JetBrains Mono", "Fira Code", ui-monospace, "Cascadia Code",
    Menlo, Consolas, monospace;

  --maxw: 1180px;
  --space: 1rem;
}

[data-theme="dark"] {
  --canvas: #1a1b17;
  --surface: #222420;
  --surface-2: #2a2c25;
  --border: #34362e;
  --border-strong: #454738;
  --ink: #ece8dd;
  --ink-2: #c7c3b6;
  --muted: #928e82;

  --row-alt: rgba(236, 232, 221, 0.04);
  --row-hover: rgba(157, 178, 149, 0.16);

  --sage: #9db295;
  --sage-strong: #b6c7ae;
  --sage-tint: #2c3728;
  --sage-ink: #16180f;

  --accent: #d47a54;
  --accent-tint: #3a271d;

  --sev-critical: #ff6b8a; /* vivid crimson-pink (6.6:1 with the dark chip text) — the top of the ramp */
  --sev-high: #ff8a4d; /* vivid orange (7.7:1) — hue-stepped off critical's crimson */
  --sev-medium: #ffc43d; /* vivid gold (11:1 with the dark chip text) */
  --sev-low: #9db295;
  --sev-info: #9a978c;
  --sev-unknown: #6f6f66; /* unassessed — muted, distinct from info */

  --ok: #7fb389;
  --warn: #e0a558;
  --danger: #e08a6f;
  --ok-tint: #24311f;
  --warn-tint: #362a1a;
  --danger-tint: #3a231c;

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 20px rgba(0, 0, 0, 0.22);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 { line-height: 1.2; color: var(--ink); font-weight: 640; letter-spacing: -0.01em; }
h1 { font-size: 1.6rem; margin: 0 0 0.2rem; }
h2 { font-size: 1.22rem; margin: 1.2rem 0 0.5rem; }
h3 { font-size: 1.02rem; margin: 0.95rem 0 0.35rem; }
p { margin: 0 0 0.7rem; }
a { color: var(--sage-strong); text-decoration: none; }
a:hover { text-decoration: underline; }
small, .small { font-size: 0.82rem; }
.muted { color: var(--muted); }
hr { border: none; border-top: 1px solid var(--border); margin: 1.05rem 0; }

/* ── Layout ─────────────────────────────────────────────────────────── */
.container { max-width: var(--maxw); margin: 0 auto; padding: 1.35rem 1.4rem 3rem; }
.stack > * + * { margin-top: 0.72rem; }
.row { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }
.row.between { justify-content: space-between; }
.spacer { flex: 1; }
.grid { display: grid; gap: 0.8rem; }
/* Let grid tracks shrink below content width so inner <pre>/tables scroll
   within their column instead of blowing out the page. */
.grid > * { min-width: 0; }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
@media (max-width: 820px) {
  .grid.cols-2, .grid.cols-3, .grid.cols-4, .grid.cols-5 { grid-template-columns: 1fr; }
}

/* ── Page header (one component for every page) ─────────────────────────
   Breadcrumb (optional) → .page-head: title block left, action cluster
   right. Actions never wrap to the left under a long title (margin-left:
   auto keeps even a wrapped line right-aligned). All header spacing lives
   here — templates carry no inline margins at this joint. */
.page-head {
  display: flex; flex-wrap: wrap; justify-content: space-between;
  align-items: flex-start; gap: 0.6rem 1rem; margin-bottom: 1rem;
}
.page-head-text { flex: 1 1 24rem; min-width: 0; }
.page-head-text h1 { margin: 0; }
.page-head-text .sub { margin: 0.15rem 0 0; color: var(--muted); }
/* Chip row inside a detail-page identity block (id badge + status chips). */
.page-head-text .chips { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-top: 0.4rem; }
.page-head-actions { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-left: auto; }

/* Segmented view switcher (reports tabs, admin subnav): btn-sm pills on
   their own row under the header; the active segment is btn-primary and
   carries aria-current="page". */
.subnav { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0 0 1rem; }

/* Per-row severity breakdown pills (lists + dashboard). */
.mini-sev { display: inline-flex; gap: 0.3rem; flex-wrap: wrap; }
.mini-sev .pill { font-size: 0.72rem; font-weight: 640; color: #fff; padding: 0.02rem 0.36rem; border-radius: 999px; }
.mini-sev .pill.sev-medium { color: #241a02; } /* dark ink on the amber chip (see .chip.sev) */
[data-theme="dark"] .mini-sev .pill { color: var(--sage-ink); }

/* ── Top nav ────────────────────────────────────────────────────────── */
.nav {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 20;
}
.nav-inner {
  max-width: var(--maxw); margin: 0 auto; padding: 0.55rem 1.4rem;
  display: flex; align-items: center; gap: 1.2rem;
}
.brand { display: flex; align-items: center; gap: 0.55rem; font-weight: 680; color: var(--ink); letter-spacing: -0.02em; }
.brand:hover { text-decoration: none; }
/* Brand mark: the AISLE glyph, tinted with the sage accent via a CSS mask so
   it themes (dark mode lightens --sage) and every .brand template stays markup-
   free. The standalone favicon is app/static/favicon.svg. */
.brand .mark {
  width: 22px; height: 22px; display: inline-block;
  background-color: var(--sage-strong);
  -webkit-mask: url("/static/icon.svg") center / contain no-repeat;
  mask: url("/static/icon.svg") center / contain no-repeat;
}
.brand .sub { color: var(--muted); font-weight: 500; }
.nav-links { display: flex; gap: 0.25rem; align-items: center; }
.nav-links a {
  color: var(--ink-2); padding: 0.35rem 0.6rem; border-radius: var(--radius-sm);
  font-size: 0.92rem; font-weight: 520;
}
.nav-links a:hover { background: var(--surface-2); text-decoration: none; }
.nav-links a.active { color: var(--ink); background: var(--sage-tint); }
/* Settings lives in .nav-right as a .btn (not .nav-links) — mirror the active
   highlight so the current page reads clearly there too. */
.nav-right .btn.active { background: var(--sage-tint); color: var(--sage-strong); border-color: transparent; }
.nav-right { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }
.avatar {
  width: 28px; height: 28px; border-radius: 50%; background: var(--sage-tint);
  color: var(--sage-strong); display: grid; place-items: center; font-size: 0.78rem;
  font-weight: 680;
}

/* ── Buttons ────────────────────────────────────────────────────────────
   One variant per role, one size per context (keep consistent everywhere):
   .btn-primary — exactly one per form context: the commit (always labelled
                  Save/Create/Add…, always RIGHTMOST in its toolbar), an index
                  page's *unique* marquee create (New project / New report /
                  Add CVE), or the active segment of a .subnav toggle.
   .btn         — everything else that acts: recurring create shortcuts
                  (Add scan / New finding), navigation shortcuts, downloads/
                  exports, Generate/Refresh-type repeatable actions, pagers,
                  inactive segments. Same label ⇒ same variant everywhere.
   .btn-ghost   — dismiss (Cancel, Clear, Sign out), icon-only row
                  micro-controls (reorder arrows), and the top nav's quiet
                  chrome (Settings, theme toggle); never a page-level action,
                  never a download.
   .btn-danger / .btn-preview — destructive / public-preview ("Preview…").
   Size: normal in page headers + section toolbars; .btn-sm only inside dense
   containers (table rows, bulk bars, card-inline forms, subnavs, pagers,
   disclosure summaries) — never for a page-level action. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  font: inherit; font-size: 0.9rem; font-weight: 560; line-height: 1;
  padding: 0.55rem 0.9rem; border-radius: var(--radius-sm);
  border: 1px solid var(--border-strong); background: var(--surface); color: var(--ink);
  cursor: pointer; transition: background 0.12s, border-color 0.12s, transform 0.02s;
  text-decoration: none;
}
.btn:hover { background: var(--surface-2); text-decoration: none; }
.btn:active { transform: translateY(0.5px); }
.btn:focus-visible { outline: none; box-shadow: var(--ring); }
.btn[disabled], .btn:disabled { opacity: 0.55; cursor: not-allowed; }
.btn-primary { background: var(--sage); border-color: var(--sage); color: var(--sage-ink); }
.btn-primary:hover { background: var(--sage-strong); border-color: var(--sage-strong); }
.btn-danger { background: transparent; border-color: var(--accent); color: var(--accent); }
.btn-danger:hover { background: var(--accent-tint); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--ink-2); }
.btn-ghost:hover { background: var(--surface-2); }
/* "Preview as public" (reports): a muted warm-beige, distinct from the cream
   default, the sage primary and the clay danger buttons — a gentle secondary
   action shown to the owner/admin on any report, public or not. */
.btn-preview { background: #e9dfc9; border-color: var(--border-strong); color: var(--ink-2); }
.btn-preview:hover { background: #e2d5b8; }
[data-theme="dark"] .btn-preview { background: #343026; color: var(--ink-2); }
[data-theme="dark"] .btn-preview:hover { background: #3d3931; }
/* btn-preview's border/text without the beige fill (scan raw download):
   transparent at rest; hover falls through to .btn:hover's surface-2 wash. */
.btn-outline { background: transparent; border-color: var(--border-strong); color: var(--ink-2); }
.btn-sm { padding: 0.32rem 0.6rem; font-size: 0.82rem; }
.btn-lg { padding: 0.7rem 1.2rem; font-size: 1rem; }
.btn-block { width: 100%; }

/* ── Cards / panels ─────────────────────────────────────────────────── */
.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 0.85rem 1rem; box-shadow: var(--shadow);
}
.card.pad-lg { padding: 1.2rem; }
/* Card/panel section label. Written as <h2 class="card-title"> so panels stay
   in the document outline (heading navigation); every declaration here —
   line-height included — overrides the h2 element defaults, so an h2 and a
   legacy div render identically. */
.card-title { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 0.45rem; font-weight: 640; line-height: 1.5; }
.panel { border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.panel > .panel-head { padding: 0.6rem 0.9rem; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 0.6rem; }
.panel > .panel-body { padding: 0.85rem; }

/* ── Stat tiles ─────────────────────────────────────────────────────── */
/* THE stat tile — big number over an uppercase label. Used on the dashboard,
   admin overview/stats, and the /cves overview (`.inset` there: recessed
   surface-2 look for tiles nested inside another framed box). */
.stat { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 0.65rem 0.85rem; }
.stat .num { font-size: 1.45rem; font-weight: 680; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.stat .num.accent { color: var(--sage-strong); }
.stat .label { font-size: 0.8rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.stat.inset { background: var(--surface-2); border-radius: var(--radius-sm); }
/* A whole stat tile as a link (dashboard "Reports in progress"). */
.stat.link { transition: border-color 0.12s; }
.stat.link, .stat.link:hover { color: var(--ink); text-decoration: none; }
.stat.link:hover { border-color: var(--border-strong); }

/* ── Tables ─────────────────────────────────────────────────────────── */
.table { width: 100%; border-collapse: collapse; font-size: 0.92rem; }
.table th, .table td { text-align: left; padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--border); vertical-align: top; }
.table th { font-size: 0.74rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 640; }
/* The `.right` utility loses to `.table th`/`.table td` on specificity, so
   re-assert it here: numeric columns and trailing action columns right-align
   as their markup intends (a shared right edge down the card, not a ragged
   one). A single-control action <form> is block by default and would pin its
   button to the cell's left, so shrink it to inline-block and let the cell's
   text-align carry it flush right. */
.table th.right, .table td.right { text-align: right; }
.table td.right > form { display: inline-block; }
/* Zebra striping (every data table) + a hover that stays clearly distinct
   over striped and plain rows alike, in light and dark. Order matters:
   :hover shares specificity with :nth-child, so it must come after. */
.table tbody tr:nth-child(even) { background: var(--row-alt); }
.table tbody tr:hover { background: var(--row-hover); }
/* overflow-x (not hidden): an over-wide table scrolls inside its rounded
   frame instead of blowing out the card. */
.table-wrap { border: 1px solid var(--border); border-radius: var(--radius); overflow-x: auto; background: var(--surface); }
.table-wrap .table th, .table-wrap .table td { padding-left: 0.75rem; padding-right: 0.75rem; }
/* Bare tables inside cards: same scroll safety, no extra chrome. */
.table-scroll { overflow-x: auto; }
/* A bare table sits directly on the card, so its outer cell edges should line
   up flush with the card's other content — labels, inputs, the inline add-row
   button. Zero the first cell's left pad and the last cell's right pad so the
   first column aligns with the inputs above and the trailing action/number
   column shares one right edge with the add-row buttons below. Bordered
   `.table-wrap` tables are untouched (their content must clear the frame). */
.table-scroll .table th:first-child, .table-scroll .table td:first-child { padding-left: 0; }
.table-scroll .table th:last-child, .table-scroll .table td:last-child { padding-right: 0; }
/* A table filling a panel: the panel supplies the frame, so the wrap sheds
   its border and the body its padding (no inline style="" hacks). */
.panel .table-wrap { border: none; }
.panel > .panel-body.flush { padding: 0; }
/* Identifier-ish tokens (public ids, hashes, key prefixes) never break
   mid-token inside tables; wide values push the table into its scroller. */
.table .mono { white-space: nowrap; }
/* Truncate long names/ids at a sensible width — ellipsis, never a
   mid-character wrap. Put the full value in the title attribute. */
.cell-clip { display: block; max-width: 18ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Chips / badges ─────────────────────────────────────────────────── */
.chip {
  display: inline-flex; align-items: center; gap: 0.3rem; font-size: 0.76rem;
  font-weight: 600; padding: 0.16rem 0.5rem; border-radius: 999px;
  border: 1px solid var(--border-strong); color: var(--ink-2); background: var(--surface-2);
  white-space: nowrap;
}
/* Uniform width: sized to the longest label ("critical"), centered, so
   severities align into a clean column in tables/lists while still sitting
   naturally inline. em-based, so it tracks the chip's own font size. */
.chip.sev { color: #fff; border: none; min-width: 5.6em; justify-content: center; }
/* Compact variant for inline lists (report builder) where uniform column
   width isn't needed and the full-width chip dwarfs the adjacent id text. */
.chip.sev.sev-sm { min-width: 0; padding: 0.05rem 0.45rem; font-size: 0.7rem; }
.sev-critical { background: var(--sev-critical); }
.sev-high { background: var(--sev-high); }
.sev-medium { background: var(--sev-medium); }
.sev-low { background: var(--sev-low); }
.sev-info { background: var(--sev-info); }
.sev-unknown { background: var(--sev-unknown); }
/* The amber-yellow medium is too light for white text — it alone pairs with
   dark ink (the dark theme's chips already do this for every severity). */
.chip.sev.sev-medium { color: #241a02; }
[data-theme="dark"] .chip.sev { color: var(--sage-ink); }

/* subtle status chips (triage / disclosure / cve) */
.chip.status { background: var(--surface-2); color: var(--ink-2); }
.chip.status.good { background: var(--ok-tint); color: var(--ok); border-color: transparent; }
.chip.status.warn { background: var(--warn-tint); color: var(--warn); border-color: transparent; }
.chip.status.bad { background: var(--danger-tint); color: var(--danger); border-color: transparent; }
.chip.status.accent { background: var(--sage-tint); color: var(--sage-strong); border-color: transparent; }

/* ── ID badge (mono, copyable) ──────────────────────────────────────── */
.id-badge {
  font-family: var(--mono); font-size: 0.82rem; color: var(--ink-2);
  background: var(--surface-2); border: 1px solid var(--border);
  padding: 0.1rem 0.42rem; border-radius: var(--radius-sm); white-space: nowrap;
}

/* ── ID as a navigate link (bold mono, no pill) ─────────────────────────
   Convention: a plain .id-badge pill is a static id label; only the copy
   variant ([data-copy]) is interactive. A plain bold monospace link (.id-link)
   means "click to open" — inherits link colour + hover underline from `a`. */
.id-link { font-family: var(--mono); font-weight: 640; font-size: 0.9rem; white-space: nowrap; }

/* ── Forms ──────────────────────────────────────────────────────────── */
/* A <form> is an action wrapper, never a spacing element: zero margin
   everywhere (templates previously repeated style="margin:0" per form). */
form { margin: 0; }
.field { margin-bottom: 0.7rem; }
.field > label { display: block; font-size: 0.84rem; font-weight: 600; color: var(--ink-2); margin-bottom: 0.28rem; }
/* Not scoped to .field: a hint keeps its quiet styling wherever it appears
   (inline form rows, preview banners) instead of silently rendering as
   full-size body text. */
.hint { font-size: 0.78rem; color: var(--muted); margin-top: 0.25rem; }
.req { color: var(--accent); }
/* Inline checkbox (e.g. the API-key "All scopes" toggle). */
.check { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.9rem; cursor: pointer; }
.check input { margin: 0; flex: none; }
/* API-key scope picker: one selectable card per scope. The checked scope is
   highlighted; hovering a card surfaces its endpoint (the card's title). */
.scope-list {
  display: flex; flex-direction: column; gap: 0.4rem;
  /* Own scroll area: the picker stays compact (about the height of the
     name/Create-key column beside it) and scrolls if more scopes are added,
     instead of growing the whole section. */
  max-height: 8rem; overflow-y: auto; padding-right: 0.15rem;
}
.scope {
  display: flex; align-items: flex-start; gap: 0.5rem;
  padding: 0.35rem 0.6rem; border: 1px solid var(--border);
  border-radius: var(--radius-sm); cursor: pointer;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.scope:hover { border-color: var(--border-strong); }
.scope:has(input:checked) { border-color: var(--sage); background: var(--sage-tint); }
.scope > input { margin: 0.1rem 0 0; flex: none; }
.scope-body { display: flex; flex-direction: column; gap: 0.05rem; min-width: 0; }
.scope-head { display: flex; align-items: center; gap: 0.45rem; font-size: 0.85rem; font-weight: 600; color: var(--ink); }
.scope-desc { font-size: 0.76rem; line-height: 1.3; color: var(--muted); }
/* API-key create form: a narrow name column (Create key sits right under it)
   beside a scopes column that fills the rest of the card, so scope
   descriptions read on one line rather than wrapping. Collapses to a single
   column on narrow screens. */
.apikey-create {
  display: grid; align-items: start; gap: 0.8rem 2.5rem;
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
}
.apikey-create > * { min-width: 0; }
@media (max-width: 820px) { .apikey-create { grid-template-columns: 1fr; } }
/* Inline "add" row shared by the Settings add-a-passkey and add-an-age-key
   forms: the primary field grows, an optional secondary field stays compact,
   the submit button sits flush at the right — one rhythm for both. Explicit
   flex on the inputs is required: a bare input's default width:100% would
   otherwise claim the whole row as its flex basis and crush its siblings.
   Wraps to stacked rows on narrow screens. */
.inline-add { display: flex; gap: 0.5rem; align-items: center; }
.inline-add > input { flex: 3 1 18rem; }
.inline-add > input.secondary { flex: 1 1 8rem; }
.inline-add > .btn { white-space: nowrap; }
@media (max-width: 700px) { .inline-add { flex-wrap: wrap; } }
input[type="text"], input[type="email"], input[type="url"], input[type="number"],
input[type="date"], input[type="search"], select, textarea {
  width: 100%; font: inherit; font-size: 0.92rem; color: var(--ink);
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 0.5rem 0.6rem;
}
textarea { min-height: 6rem; resize: vertical; line-height: 1.5; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--sage); box-shadow: var(--ring); }
/* Readonly/disabled controls read as inert: greyed, muted text, no text caret,
   no focus emphasis — the value is display-only (edits go through a dedicated
   action such as "Unset"/"Unlink", never free-typing over a live link). */
input[readonly], textarea[readonly], input:disabled, select:disabled, textarea:disabled {
  background: var(--surface-2); color: var(--muted);
  border-color: var(--border); cursor: default;
}
input[readonly]:focus, textarea[readonly]:focus { border-color: var(--border); box-shadow: none; }
/* <summary class="btn"> disclosures (Unset / Make public / delete): drop the
   native triangle marker and complete the button affordance so they read —
   and behave — as plain buttons. */
summary.btn { list-style: none; display: inline-flex; cursor: pointer; }
summary.btn::-webkit-details-marker { display: none; }
.input-invalid { border-color: var(--danger) !important; }
label.check { display: inline-flex; align-items: center; gap: 0.45rem; font-size: 0.9rem; font-weight: 500; }
label.check.check-plain { font-weight: normal; }
label.check input { width: auto; }
.mono { font-family: var(--mono); }

/* ── Filter toolbar ─────────────────────────────────────────────────── */
/* One compact row of auto-applying controls (no submit button); wraps on
   small screens. Visible labels are dropped — controls carry aria-label,
   a placeholder, or an "All …" empty option instead. */
.filters {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem;
  padding: 0.4rem 0.5rem; margin: 0.7rem 0 0.8rem;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius);
}
.filters select, .filters input {
  width: auto; font-size: 0.85rem; padding: 0.34rem 0.5rem;
  background: var(--canvas);
}
.filters select { max-width: 13rem; }
/* The free-text search grows to soak up the row; narrow token inputs
   (CWE / scan id / project) stay fixed. */
.filters input[type="search"] { flex: 1 1 13rem; min-width: 9rem; }
.filters .w-sm { width: 7rem; }
.filters .w-md { width: 10.5rem; }
.filters-label {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); font-weight: 640; padding: 0 0.25rem 0 0.15rem;
}
.filters .clear {
  font-size: 0.82rem; color: var(--muted); white-space: nowrap;
  padding: 0.2rem 0.4rem; border-radius: var(--radius-sm);
}
.filters .clear:hover { color: var(--accent); background: var(--surface-2); text-decoration: none; }

/* ── Alerts / flash ─────────────────────────────────────────────────── */
/* Plain block, NOT flex: banners carry rich inline content (<strong>, links)
   that must flow as normal text — flex turned each inline child into its own
   gapped item. Links inside a tinted banner are underlined so they read as
   links despite the low-contrast wash. */
.alert { border: 1px solid var(--border); border-radius: var(--radius); padding: 0.55rem 0.8rem; font-size: 0.9rem; }
.alert a { text-decoration: underline; }
.alert.info { background: var(--sage-tint); border-color: transparent; color: var(--sage-strong); }
.alert.success { background: var(--ok-tint); border-color: transparent; color: var(--ok); }
.alert.warn { background: var(--warn-tint); border-color: transparent; color: var(--warn); }
.alert.error { background: var(--danger-tint); border-color: transparent; color: var(--danger); }

/* Floating confirm/menu panel inside an open <details> (Unset / Unlink /
   Make public / Submit): anchors under the summary's row, never reflows the
   page. Width via the size modifiers; app.js closes it on outside click. */
.popover {
  display: block; position: absolute; right: 0; top: calc(100% + 0.4rem);
  z-index: 20; width: 24rem; max-width: 80vw;
}
.popover.popover-lg { width: 30rem; }
.popover-list { margin: 0 0 0.7rem; padding-left: 1.1rem; line-height: 1.5; }

/* Report-builder one-line rows (selected items / candidates): text grows, action slot fixed right. */
.item-row { display: flex; flex-wrap: nowrap; align-items: center; gap: 1rem; }
.cand-row { display: flex; flex-wrap: nowrap; align-items: center; gap: 0.8rem; padding: 0.5rem 0; border-top: 1px solid var(--border); }

/* Confirm disclosure whose panel lives OUTSIDE its button row: the <details>
   (.confirm-toggle) holds only the summary, and the panel (.confirm-below) is
   the row's next sibling. Opening the toggle reveals the panel in normal flow
   below the row — the card grows downward, the sibling buttons never move. */
.confirm-below { display: none; }
.row:has(> details.confirm-toggle[open]) + .confirm-below { display: block; }

/* ── Code ───────────────────────────────────────────────────────────── */
code { font-family: var(--mono); font-size: 0.86em; background: var(--surface-2); padding: 0.08rem 0.32rem; border-radius: 4px; }
pre { font-family: var(--mono); font-size: 0.82rem; line-height: 1.5; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: 0.9rem 1rem; overflow: auto; }
pre code { background: none; padding: 0; }
.filepath { font-family: var(--mono); font-size: 0.82rem; color: var(--muted); }

/* ── Overlay override indicator ─────────────────────────────────────── */
.reset-link {
  font-size: 0.76rem; color: var(--muted); cursor: pointer;
  background: none; border: none; padding: 0; font-family: inherit; white-space: nowrap;
}
.reset-link:hover { color: var(--accent); text-decoration: underline; }

/* ── Finding detail: compact overlay editor ─────────────────────────── */
/* Shared by BOTH overlay editors (finding detail + CVE editor): wide field
   column, narrow rail. */
.editor-layout { display: grid; grid-template-columns: 1.7fr 1fr; gap: 1.2rem; align-items: start; }
/* Sticky right-rail preview (report builder + CVE editor): stays in view
   while the field column scrolls, scrolling internally when taller than
   the viewport. */
.preview-sticky { position: sticky; top: 4.5rem; max-height: calc(100vh - 6rem); overflow: auto; }
@media (max-width: 900px) { .preview-sticky { position: static; max-height: none; } }
@media (max-width: 900px) { .editor-layout { grid-template-columns: 1fr; } }
/* Tighter cards/panels on the editor pages: the per-field rows carry their
   own rhythm, so the containers shed most of their padding. */
.editor-layout .card { padding: 0.75rem 1rem 0.5rem; }
.editor-layout .card-title { margin-bottom: 0.15rem; }
.editor-layout .panel > .panel-body { padding: 0.85rem 1rem; }

.ovl-field { padding: 0.42rem 0; border-bottom: 1px solid var(--border); }
.ovl-field:last-child { border-bottom: none; }
/* One row per compact field: fixed label rail | control. Extras (scanner
   base preview, tag pills) hang under the control column. */
.ovl-row { display: grid; grid-template-columns: 7.5rem minmax(0, 1fr); gap: 0.12rem 0.7rem; align-items: center; }
.ovl-label {
  font-size: 0.74rem; font-weight: 640; color: var(--ink-2);
  text-transform: uppercase; letter-spacing: 0.04em; margin: 0; min-width: 0;
  display: inline-flex; align-items: center; gap: 0.35rem;
}
.ovl-control { display: flex; gap: 0.5rem; align-items: center; min-width: 0; }
/* Controls are bound to the page-level form via the HTML5 ``form=`` attribute
   (no per-field <form> wrappers), so they sit directly in .ovl-control. */
.ovl-control select, .ovl-control input { flex: 1 1 auto; min-width: 0; padding: 0.32rem 0.5rem; font-size: 0.88rem; }
/* Client-side constraint feedback (pattern / min / max) and a server-rejected
   value (input-invalid, added by app.js from the bulk-save response): the
   border says why. */
.ovl-control input:invalid { border-color: var(--danger); }
.ovl-row .ovl-extra { grid-column: 2; }
@media (max-width: 700px) {
  .ovl-row { grid-template-columns: 1fr; }
  .ovl-row .ovl-extra { grid-column: 1; }
}
/* Long-form fields (markdown/code) stay stacked; the control is a plain
   textarea (global textarea rule above already makes it full-width) bound to
   the page-level form via ``form=``, same as any compact field. */
.ovl-head { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.3rem; }
/* References: a repeatable set of discrete URL rows (one <input> each) plus an
   "Add URL" button — never a comma box, since a reference URL may itself
   contain a comma. Shared by the registry editor (a bordered field in a card)
   and the new-CVE form (a standalone field). */
.refs-list { display: flex; flex-direction: column; gap: 0.4rem; }
.refs-row { display: flex; gap: 0.5rem; align-items: center; min-width: 0; }
.refs-row input { flex: 1 1 auto; min-width: 0; padding: 0.32rem 0.5rem; font-size: 0.88rem; }
.refs-actions { margin-top: 0.45rem; }
.refs-list:empty + .refs-actions { margin-top: 0; }
/* On the create form the widget stands alone (no editor field-list around it),
   so it reads like a normal .field: no divider, title-case label. */
.refs:not([data-refs-base]) { border-bottom: none; padding: 0; margin-bottom: 0.7rem; }
.refs:not([data-refs-base]) .ovl-label { text-transform: none; letter-spacing: 0; font-size: 0.84rem; }
.hist { list-style: none; margin: 0; padding: 0; }
.hist li { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0; border-bottom: 1px solid var(--border); font-size: 0.84rem; }
.hist li:last-child { border-bottom: none; }
/* Field name, op chip and timestamp keep their width; only the value shrinks —
   ellipsized on one line, so a long description or reference list never wraps
   the row tall or pushes the timestamp out of the panel. */
.hist li > * { flex: none; }
.hist li .spacer { flex: 1; }
.hist .hist-val { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Pagination bar (one anatomy everywhere) ────────────────────────────
   Always-rendered Prev/Next (disabled = span[aria-disabled]) around a
   centered count label, so the controls never jump between pages. */
.pager { display: flex; align-items: center; gap: 0.7rem; margin-top: 0.8rem; }
.pager .btn[aria-disabled="true"] { opacity: 0.45; }

/* ── Meter / severity distribution bar ──────────────────────────────── */
.meter { display: flex; height: 10px; border-radius: 999px; overflow: hidden; background: var(--surface-2); border: 1px solid var(--border); }
.meter > span { display: block; height: 100%; }

/* ── Charts (server-rendered SVG) ───────────────────────────────────── */
.donut { display: block; }
.donut-num { fill: var(--ink); font-weight: 680; font-size: 1.3rem; font-family: var(--font); }
.donut-sub { fill: var(--muted); font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.06em; font-family: var(--font); }
.legend { display: flex; gap: 0.9rem; flex-wrap: wrap; font-size: 0.82rem; color: var(--ink-2); }
.legend-item { display: inline-flex; align-items: center; gap: 0.35rem; white-space: nowrap; }
.legend-dot { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }
.minibar { display: inline-flex; height: 8px; border-radius: 999px; overflow: hidden; background: var(--surface-2); vertical-align: middle; }
.minibar > span { display: block; height: 100%; }
th.sort a { color: var(--muted); text-decoration: none; white-space: nowrap; }
th.sort a:hover { color: var(--ink); }
th.sort.active a { color: var(--ink); }

/* ── Analytics primitives (per-scan / dashboard / admin) ────────────────── */
.analytics .grid { align-items: start; }
.chart-cell { display: flex; flex-direction: column; align-items: center; gap: 0.7rem; }
.chart-cell .legend { justify-content: center; }
.chart-cell .meter { width: 100%; }
.fp-hero { text-align: center; }
.fp-num { font-size: 2.6rem; font-weight: 680; letter-spacing: -0.02em; line-height: 1; color: var(--ink); }
.fp-num .pct { font-size: 1.2rem; font-weight: 600; color: var(--muted); margin-left: 0.1rem; }
.fp-label { font-size: 0.76rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin-top: 0.25rem; }
.ministats { display: flex; gap: 0.4rem; margin-top: 0.9rem; flex-wrap: wrap; justify-content: center; }
.ministat { text-align: center; min-width: 3.4rem; padding: 0.35rem 0.5rem; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-2); }
.ministat .n { font-size: 1.1rem; font-weight: 680; color: var(--ink); }
.ministat .l { font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }

/* ── CVE registry overview (stats header on /cves) ──────────────────────── */
.cve-stats { padding: 0.9rem 1.1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); display: flex; flex-direction: column; gap: 0.85rem; }
.cve-stats-main { display: flex; align-items: center; gap: 1.6rem; flex-wrap: wrap; }
.cve-stats-donut { flex: 0 0 auto; }
.cve-stats-body { flex: 1 1 17rem; display: flex; flex-direction: column; gap: 0.95rem; min-width: 0; }
.cve-stats-tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.7rem; }
.cve-stats-legend .legend { gap: 1rem; }
.cve-stats-years { display: flex; align-items: stretch; gap: 1.1rem; border-top: 1px solid var(--border); padding-top: 0.95rem; }
.cve-stats-years-label { flex: 0 0 auto; align-self: center; }
.year-bars { display: flex; align-items: flex-end; gap: 1rem; flex: 1 1 auto; overflow-x: auto; }
.year-bar { display: flex; flex-direction: column; align-items: center; gap: 0.28rem; flex: 0 0 auto; width: 2.7rem; }
.year-bar-track { width: 100%; height: 48px; display: flex; align-items: flex-end; background: var(--surface-2); border-radius: var(--radius-sm); overflow: hidden; }
.year-bar-fill { width: 100%; background: var(--sage); border-radius: var(--radius-sm) var(--radius-sm) 0 0; min-height: 3px; }
.year-bar-count { font-size: 0.72rem; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.year-bar-year { font-size: 0.7rem; color: var(--muted); font-variant-numeric: tabular-nums; }
@media (max-width: 640px) { .cve-stats-tiles { grid-template-columns: repeat(2, 1fr); } }

.cve-refs { margin: 0.4rem 0 0; padding-left: 1.1rem; }
.cve-refs li { margin: 0.18rem 0; word-break: break-all; }

/* /cves registry table: fixed layout so it always fits its container (no
   horizontal scrollbar); flexible text columns ellipsis-clip via .trunc.
   Fixed columns are sized to their widest realistic content PLUS the
   .table-wrap cell padding (2 x 0.75rem), so nowrap content (dates, ids,
   chips) never eats the row's symmetric edge padding: a 6-digit CVE id needs
   ~8.5rem of text, "2026-12-31" ~4.75rem, the "critical" chip ~4.3rem
   (measured in Chromium; overflow:hidden would clip flush otherwise). */
.cve-table { table-layout: fixed; width: 100%; }
.cve-table th, .cve-table td { overflow: hidden; }
.cve-table col.c-cve { width: 10rem; }
.cve-table col.c-product { width: 14%; }
.cve-table col.c-sev { width: 6rem; }
.cve-table col.c-vis { width: 8.5rem; }
.cve-table col.c-owner { width: 8rem; }
.cve-table col.c-updated { width: 7rem; }
.trunc { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; max-width: 100%; }
/* a registry row missing a field we always want — thin red left edge */
.row-incomplete td:first-child { box-shadow: inset 3px 0 0 var(--sev-critical); }


/* ── Empty state ────────────────────────────────────────────────────── */
.empty { text-align: center; padding: 3rem 1rem; color: var(--muted); }
.empty .big { font-size: 1.05rem; color: var(--ink-2); margin-bottom: 0.3rem; }

/* ── Auth screens ───────────────────────────────────────────────────── */
.auth-wrap { min-height: 78vh; display: grid; place-items: center; }
.auth-card { width: 100%; max-width: 400px; }
.auth-card .brand { justify-content: center; margin-bottom: 1.2rem; }

/* ── Misc utilities ─────────────────────────────────────────────────────
   Spacing helpers so templates never carry inline style= margins: the 1rem
   section rhythm (.mt-1/.mb-1) plus its recurring fractions. Bottom margins
   are declared after .m-0 so "m-0 mb-06" composes. */
.m-0 { margin: 0; }
.mt-02 { margin-top: 0.2rem; }
.mt-04 { margin-top: 0.4rem; }
.mt-06 { margin-top: 0.6rem; }
.mt-07 { margin-top: 0.7rem; }
.mt-1 { margin-top: 1rem; }
.mt-pull { margin-top: -0.4rem; } /* tuck a sub-line under its heading */
.mb-0 { margin-bottom: 0; }
.mb-04 { margin-bottom: 0.4rem; }
.mb-06 { margin-bottom: 0.6rem; }
.mb-08 { margin-bottom: 0.8rem; }
.mb-1 { margin-bottom: 1rem; }
.mb-12 { margin-bottom: 1.2rem; }
.mb-14 { margin-bottom: 1.4rem; }
.mr-06 { margin-right: 0.6rem; }
.mt-08 { margin-top: 0.8rem; }
.mt-085 { margin-top: 0.85rem; }
.mt-09 { margin-top: 0.9rem; }
.mb-025 { margin-bottom: 0.25rem; }
.mb-07 { margin-bottom: 0.7rem; }
.grow { flex: 1; }
.center { text-align: center; }
.relative { position: relative; }
.self-start { align-self: flex-start; }
.items-start { align-items: flex-start; } /* flex rows and grid tracks alike */
.items-end { align-items: flex-end; }
.items-stretch { align-items: stretch; }
.span-2 { grid-column: span 2; }
.break-all { word-break: break-all; }
.inline { display: inline; }
.nowrap-wrap { flex-wrap: nowrap; }
.fw-normal { font-weight: 400; }
.gap-04 { gap: 0.4rem; }
.gap-03 { gap: 0.3rem; }
.gap-05 { gap: 0.5rem; }
.gap-14 { gap: 1.4rem; }
.gap-25 { gap: 2.5rem; }
.list-plain { list-style: none; padding: 0; }
.narrow-form { max-width: 34rem; }
/* report builder: fixed-width right-hand action slots in item/candidate rows */
.item-actions { flex: 0 0 auto; }
.cand-actions { flex: 0 0 auto; min-width: 4.5rem; text-align: right; }
.cand-stack > * + * { margin-top: 0.5rem; }  /* tighter than the default .stack rhythm */
.wall-card-preview { max-width: 22rem; }
/* Fixed-width tick/cross chip (the CVE checklist) — count stays centered. */
.chip.chip-tick { min-width: 1.4rem; justify-content: center; }
.narrow { max-width: 720px; }
.narrow-sm { max-width: 640px; }
.narrow-xs { max-width: 32rem; }
.my-02 { margin: 0.15rem 0; }
/* A button beside a full-height input in a .row: stretch to match. */
.btn-fill { height: 100%; white-space: nowrap; }
/* Emphasized one-shot link card (admin invite/reset links). */
.card.callout { border-color: var(--sage); background: var(--sage-tint); margin: 1rem 0; }
.card.callout .card-title { color: var(--sage-strong); }
/* Warm variant — the passkey-reset link card (distinct from the sage invite/
   key cards, since a reset is a more sensitive one-shot credential). */
.card.callout-warn { border-color: var(--warn); background: var(--warn-tint); margin: 1rem 0; }
.card.callout-warn .card-title { color: var(--warn); }
.tag { display: inline-block; font-size: 0.74rem; background: var(--surface-2); border: 1px solid var(--border); color: var(--ink-2); padding: 0.08rem 0.4rem; border-radius: 4px; margin: 0 0.2rem 0.2rem 0; }
.breadcrumb { font-size: 0.85rem; color: var(--muted); margin-bottom: 0.6rem; }
.breadcrumb a { color: var(--muted); }
.right { text-align: right; }
.nowrap { white-space: nowrap; }
.hidden { display: none !important; }
.divide-top { border-top: 1px solid var(--border); padding-top: 1rem; margin-top: 1rem; }
[hidden] { display: none !important; }
.htmx-indicator { opacity: 0; transition: opacity 0.15s; }
.htmx-request .htmx-indicator, .htmx-indicator.htmx-request { opacity: 1; }
