/* PkgFire dashboard.
   One stylesheet, no build step. Alpine drives interactivity; this file only
   describes appearance.

   Everything here is class-driven, deliberately. The pages are served with
   `style-src 'self'`, which blocks inline `style=` ATTRIBUTES and not only
   <style> blocks, so a declaration written on an element is dropped silently
   and the layout on screen is not the layout the template describes. 35 of
   them had accumulated before anyone noticed. If a rule is needed, it gets a
   class here; there is no other option that works.

   Structure:
     1. tokens          - spacing, type, colour roles, radii, elevation
     2. base            - resets and element defaults
     3. shell           - rail, content column, page header
     4. surfaces        - panel, card, section, empty state
     5. data            - stats, tables, lists, meta
     6. status          - badges, severities, verdicts, tones
     7. scan detail     - verdict band, reasons, findings
     8. forms           - inputs, buttons, toolbars
     9. blocks          - alerts, command boxes
    10. public          - landing, auth
    11. responsive
*/

/* ============================================================ 1. tokens */

:root {
  /* Spacing. One scale, used everywhere; no per-element margins. */
  --s1:  4px;
  --s2:  8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;
  --s8: 72px;

  /* Type. Named by role so a size change is a decision, not a guess. */
  --t-micro: 10.5px;  /* badge text */
  --t-tiny:  11.5px;  /* table headers, legends */
  --t-small: 12.5px;  /* hints, secondary meta */
  --t-body:  13.5px;  /* table cells, form text */
  --t-lead:  15px;    /* body copy */
  --t-h3:    14px;
  --t-h2:    17px;
  --t-h1:    26px;
  --t-display: 34px;  /* stat numbers, landing headline */

  --lh-tight: 1.25;
  --lh-body:  1.55;

  /* Colour roles. Surfaces step from the page ground upward; text steps from
     primary down. Naming them by role rather than by colour is what lets the
     dark theme be a token swap instead of a second stylesheet. */
  --surface-0: #f7f6f3;  /* page ground */
  --surface-1: #ffffff;  /* raised: cards, panels, rail */
  --surface-2: #f2efea;  /* recessed fill: table heads, code, wells */
  --surface-3: #ebe7e0;  /* hover */

  --border:        #e5e0d8;
  --border-strong: #d4cdc1;

  --text-1: #191712;  /* primary */
  --text-2: #514a40;  /* secondary */
  --text-3: #837a6d;  /* muted */

  --accent:      #c2410c;
  --accent-ink:  #9a3412;  /* accent text on a light ground, AA-safe */
  --accent-soft: #fdf0e8;
  --accent-fg:   #ffffff;

  --allow:      #15803d;
  --allow-soft: #ecfaf0;
  --warn:       #a95a09;
  --warn-soft:  #fdf6e9;
  --block:      #b91c1c;
  --block-soft: #fdf1f1;

  --r-sm: 6px;
  --r:    10px;
  --r-lg: 14px;
  --r-pill: 999px;

  --e1: 0 1px 2px rgba(25, 23, 18, .04);
  --e2: 0 1px 2px rgba(25, 23, 18, .04), 0 2px 6px rgba(25, 23, 18, .05);

  --rail-w: 236px;
  --content-max: 1320px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --surface-0: #111110;
    --surface-1: #1a1816;
    --surface-2: #232019;
    --surface-3: #2d2921;

    --border:        #302b24;
    --border-strong: #423b32;

    --text-1: #f6f1e9;
    --text-2: #c9c1b4;
    --text-3: #938a7d;

    --accent:      #fb923c;
    --accent-ink:  #fdba74;
    --accent-soft: #2a1a10;
    --accent-fg:   #1a1005;

    --allow:      #4ade80;
    --allow-soft: #102117;
    --warn:       #fbbf24;
    --warn-soft:  #281f10;
    --block:      #f87171;
    --block-soft: #2a1414;

    --e1: 0 1px 2px rgba(0, 0, 0, .35);
    --e2: 0 1px 2px rgba(0, 0, 0, .35), 0 2px 8px rgba(0, 0, 0, .3);
  }
}

/* ============================================================== 2. base */

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

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

body {
  margin: 0;
  min-height: 100vh;
  background: var(--surface-0);
  color: var(--text-1);
  font: var(--t-lead)/var(--lh-body) ui-sans-serif, system-ui, -apple-system,
        "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
}

code, pre, .mono {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: .875em;
  font-variant-ligatures: none;
}
code {
  background: var(--surface-2);
  border: 1px solid var(--border);
  padding: 1px 5px;
  border-radius: 4px;
  word-break: break-word;
}

a { color: var(--accent-ink); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { line-height: var(--lh-tight); }
h1 { font-size: var(--t-h1); margin: 0; letter-spacing: -.021em; font-weight: 660; }
h2 { font-size: var(--t-h2); margin: 0; letter-spacing: -.012em; font-weight: 640; }
h3 { font-size: var(--t-h3); margin: 0; font-weight: 640; }
p  { margin: 0 0 var(--s3); }
p:last-child { margin-bottom: 0; }

.lede   { color: var(--text-2); font-size: var(--t-body); margin: 0; }
.hint   { color: var(--text-3); font-size: var(--t-small); line-height: 1.5; margin: 0; }
.hint + .hint { margin-top: var(--s2); }
.muted  { color: var(--text-3); }
/* Pushes whatever follows it to the far end of a flex row. Used in toolbars,
   panel headers and list rows, so it is defined once rather than per block. */
.spacer { flex: 1; }
.nowrap { white-space: nowrap; }
.center { text-align: center; }

/* Vertical rhythm comes from the flow, not from margins on each element.
   This is what the blocked inline styles were trying to do by hand. */
.stack        > * + * { margin-top: var(--s3); }
.stack-tight  > * + * { margin-top: var(--s2); }
.stack-loose  > * + * { margin-top: var(--s5); }

/* ============================================================= 3. shell */

body.app {
  display: grid;
  grid-template-columns: var(--rail-w) minmax(0, 1fr);
}

.shell { display: flex; flex-direction: column; min-height: 100vh; min-width: 0; }

/* The rail. A permanent left nav is what makes this read as an application
   rather than a phone layout stretched onto a desktop: it uses the horizontal
   space, keeps orientation visible, and gives the content column its full
   height back. */
.rail {
  position: sticky; top: 0; z-index: 20;
  height: 100vh;
  display: flex; flex-direction: column;
  gap: var(--s5);
  padding: var(--s5) var(--s3);
  background: var(--surface-1);
  border-right: 1px solid var(--border);
}

.rail-brand {
  display: flex; align-items: center; gap: var(--s2);
  padding: 0 var(--s3);
  font-size: var(--t-lead); font-weight: 670; letter-spacing: -.01em;
  color: var(--text-1);
}
.rail-brand:hover { text-decoration: none; }
.rail-brand svg { display: block; flex: none; }

.rail nav { display: flex; flex-direction: column; gap: 1px; }
.rail nav a {
  display: flex; align-items: center; gap: var(--s3);
  padding: 7px var(--s3);
  border-radius: var(--r-sm);
  color: var(--text-2); font-size: var(--t-body); font-weight: 520;
}
.rail nav a:hover { background: var(--surface-2); color: var(--text-1); text-decoration: none; }
.rail nav a.on { background: var(--accent-soft); color: var(--accent-ink); font-weight: 620; }
.rail nav svg { flex: none; opacity: .8; }
.rail nav a.on svg { opacity: 1; }

.rail-foot {
  margin-top: auto;
  padding: var(--s3) var(--s3) 0;
  border-top: 1px solid var(--border);
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
}
.who {
  color: var(--text-3); font-size: var(--t-small);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  flex: 1;
}

.wrap {
  flex: 1;
  width: 100%; max-width: var(--content-max);
  padding: var(--s6) var(--s6) var(--s7);
}
.foot {
  max-width: var(--content-max);
  padding: 0 var(--s6) var(--s5);
  color: var(--text-3); font-size: var(--t-small);
}

/* The page header carries the one-line answer to "where am I and what is
   this", and is the only place an h1 appears. */
.page-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s5); flex-wrap: wrap;
  padding-bottom: var(--s5);
  margin-bottom: var(--s5);
  border-bottom: 1px solid var(--border);
}
.page-head > div { min-width: 0; }
.page-head h1 + .lede { margin-top: var(--s2); max-width: 68ch; }
.page-head-actions { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }

.crumb { margin: 0 0 var(--s3); font-size: var(--t-body); }
.crumb a { color: var(--text-3); }
.crumb a:hover { color: var(--text-1); }

/* ========================================================== 4. surfaces */

/* Three weights, and the difference between them is the hierarchy. A page
   where every block is the same bordered box leads the eye nowhere.

   .panel  - a container for data. Bordered, raised, content flush to edges.
   .card   - a panel with padding, for prose and forms.
   .well   - recessed, for supporting detail that is not the point.        */

.panel {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--e1);
  overflow: hidden;
}
.panel-head {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.panel-head h2, .panel-head h3 { font-size: var(--t-h3); }
.panel-head .spacer { flex: 1; }
.panel-body { padding: var(--s4); }
/* A footer, not a second header: the rule goes above it. Reusing .panel-head
   here drew the border under the Save button instead of over it. */
.panel-foot {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3) var(--s4);
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--e1);
  padding: var(--s5);
}

.well {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: var(--s4);
}

.sec { min-width: 0; }
.sec + .sec { margin-top: var(--s6); }
.sec-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s3);
}
.sec-head .hint { flex: none; }

.empty {
  background: var(--surface-1);
  border: 1px dashed var(--border-strong);
  border-radius: var(--r);
  padding: var(--s7) var(--s5);
  text-align: center; color: var(--text-3);
}
.empty p { margin: 0; }
.empty .hint { margin-top: var(--s2); }

/* Content columns. `minmax(0, …)` on the main column so a wide table scrolls
   inside its own container instead of stretching the grid. */
.cols {
  display: grid;
  grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr);
  gap: var(--s6);
  align-items: start;
}
/* Two equal columns. Carries its own display/gap rather than needing .cols
   alongside it - written as a modifier once, it silently stacked. */
.cols-even {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4);
  align-items: start;
}

/* A fixed-width aside for reference material, next to a fluid main column. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--s6);
  align-items: start;
}
.split-aside { position: sticky; top: var(--s5); display: grid; gap: var(--s4); }

/* ============================================================== 5. data */

.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s3);
  margin-bottom: var(--s6);
}
.stat {
  position: relative; overflow: hidden;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--e1);
  padding: var(--s4) var(--s4) var(--s4) var(--s5);
}
.stat::before {
  content: ""; position: absolute; inset: 0 auto 0 0;
  width: 3px; background: var(--border-strong);
}
.stat-allow::before { background: var(--allow); }
.stat-warn::before  { background: var(--warn); }
.stat-block::before { background: var(--block); }

.stat-n {
  display: block;
  font-size: var(--t-display); font-weight: 620;
  letter-spacing: -.03em; line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.stat-l {
  display: block; margin-top: var(--s1);
  color: var(--text-3); font-size: var(--t-small);
  text-transform: uppercase; letter-spacing: .05em; font-weight: 580;
}
.stat-allow .stat-n { color: var(--allow); }
.stat-warn  .stat-n { color: var(--warn); }
.stat-block .stat-n { color: var(--block); }

.tablewrap {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--r);
  box-shadow: var(--e1);
  overflow: hidden;
}
/* A table is the one thing allowed to scroll sideways on a narrow screen. */
.tablewrap.scrolls { overflow-x: auto; }

.grid { width: 100%; border-collapse: collapse; }
.grid th, .grid td {
  text-align: left;
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border);
}
.grid th {
  font-size: var(--t-tiny); text-transform: uppercase; letter-spacing: .055em;
  color: var(--text-3); font-weight: 640;
  background: var(--surface-2);
  position: sticky; top: 0; z-index: 1;
  white-space: nowrap;
}
.grid td { font-size: var(--t-body); }
.grid tbody tr:last-child td { border-bottom: none; }
.grid tbody tr:hover { background: var(--surface-2); }
.grid tr.dim { opacity: .55; }

/* Sortable headers. The indicator is driven by aria-sort, which the table
   component sets - so the accessible state and the visible state cannot
   disagree, and neither needs an inline style. */
.grid th[data-sort] { cursor: pointer; user-select: none; }
.grid th[data-sort]:hover { color: var(--text-1); background: var(--surface-3); }
.grid th[data-sort]::after {
  content: "↕"; margin-left: var(--s1);
  opacity: .3; font-size: 10px; font-weight: 400;
}
.grid th[aria-sort="ascending"]  { color: var(--text-1); }
.grid th[aria-sort="descending"] { color: var(--text-1); }
.grid th[aria-sort="ascending"]::after  { content: "↑"; opacity: 1; color: var(--accent); }
.grid th[aria-sort="descending"]::after { content: "↓"; opacity: 1; color: var(--accent); }
.grid .num { font-variant-numeric: tabular-nums; }

.list { list-style: none; padding: 0; margin: 0; }
.list li {
  display: flex; gap: var(--s2); align-items: baseline; flex-wrap: wrap;
  padding: var(--s3) 0;
  border-bottom: 1px solid var(--border);
}
.list li:first-child { padding-top: 0; }
.list li:last-child { border-bottom: none; padding-bottom: 0; }

.meta { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--s2) var(--s4); margin: 0; }
.meta dt { color: var(--text-3); font-size: var(--t-small); }
.meta dd { margin: 0; font-size: var(--t-body); min-width: 0; word-break: break-word; }
/* In a narrow aside the two-column form runs out of room and the values wrap
   against their own labels. Stacked reads better there. */
.meta-stack { grid-template-columns: minmax(0, 1fr); gap: var(--s3); }
.meta-stack dt { margin-bottom: 2px; }

/* ============================================================ 6. status */

.badge, .sev, .status {
  display: inline-flex; align-items: center; gap: var(--s1);
  padding: 2.5px var(--s2);
  border-radius: var(--r-pill);
  font-size: var(--t-micro); font-weight: 680; letter-spacing: .055em;
  text-transform: uppercase; white-space: nowrap;
  border: 1px solid transparent;
}
.badge-allow { background: var(--allow-soft); color: var(--allow); border-color: color-mix(in srgb, var(--allow) 25%, transparent); }
.badge-warn  { background: var(--warn-soft);  color: var(--warn);  border-color: color-mix(in srgb, var(--warn) 25%, transparent); }
.badge-block { background: var(--block-soft); color: var(--block); border-color: color-mix(in srgb, var(--block) 25%, transparent); }
.badge-muted { background: var(--surface-2);  color: var(--text-3); border-color: var(--border); }

.sev-critical { background: var(--block-soft); color: var(--block); border-color: color-mix(in srgb, var(--block) 40%, transparent); }
.sev-high     { background: var(--block-soft); color: var(--block); border-color: color-mix(in srgb, var(--block) 18%, transparent); }
.sev-medium   { background: var(--warn-soft);  color: var(--warn);  border-color: color-mix(in srgb, var(--warn) 22%, transparent); }
.sev-low      { background: var(--surface-2);  color: var(--text-3); border-color: var(--border); }
.sev-info     { background: var(--surface-2);  color: var(--text-3); border-color: var(--border); }

.status-ok      { background: var(--allow-soft); color: var(--allow); border-color: color-mix(in srgb, var(--allow) 25%, transparent); }
.status-bad     { background: var(--block-soft); color: var(--block); border-color: color-mix(in srgb, var(--block) 25%, transparent); }
.status-pending { background: var(--warn-soft);  color: var(--warn);  border-color: color-mix(in srgb, var(--warn) 25%, transparent); }

/* Tone classes carry a verdict's colour onto a whole surface. The template
   gets the tone word from a template helper, so the mapping from verdict to
   colour lives in one place in Go rather than in every page. */
.tone-allow { --tone: var(--allow); --tone-soft: var(--allow-soft); }
.tone-warn  { --tone: var(--warn);  --tone-soft: var(--warn-soft); }
.tone-block { --tone: var(--block); --tone-soft: var(--block-soft); }
.tone-muted { --tone: var(--border-strong); --tone-soft: var(--surface-2); }

/* ======================================================= 7. scan detail */

/* The verdict band is the page. Someone arriving from an alert email needs
   the answer before they need anything else, so it is the widest, loudest
   element on screen and it is coloured by the verdict itself. */
.verdict-band {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s5);
  align-items: center;
  padding: var(--s5) var(--s5) var(--s5) var(--s6);
  margin-bottom: var(--s5);
  position: relative; overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--tone) 28%, transparent);
  border-radius: var(--r-lg);
  background: var(--tone-soft);
}
.verdict-band::before {
  content: ""; position: absolute; inset: 0 auto 0 0;
  width: 5px; background: var(--tone);
}
.verdict-word {
  display: block;
  font-size: var(--t-display); font-weight: 700;
  letter-spacing: -.02em; line-height: 1.05;
  color: var(--tone);
  text-transform: uppercase;
}
.verdict-sub { margin-top: var(--s2); color: var(--text-2); font-size: var(--t-body); }
.verdict-sub .sep { color: var(--text-3); padding: 0 var(--s1); }

/* The band as a link, on a project page: the latest scan is one click away
   and the whole surface is the target. */
a.verdict-band-link { color: inherit; }
a.verdict-band-link:hover { text-decoration: none; border-color: color-mix(in srgb, var(--tone) 55%, transparent); }
a.verdict-band-link .verdict-sub a { color: inherit; }

.project-facts { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.project-facts a { word-break: break-all; }
.tone-block-text { color: var(--block); font-weight: 620; }

.panel-danger { border-color: color-mix(in srgb, var(--block) 30%, var(--border)); }
.panel-danger .panel-head { background: var(--block-soft); }

.risk {
  text-align: right; flex: none;
  padding-left: var(--s5);
  border-left: 1px solid color-mix(in srgb, var(--tone) 22%, transparent);
}
.risk-n {
  display: block;
  font-size: var(--t-display); font-weight: 650; line-height: 1.05;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums;
  color: var(--text-1);
}
.risk-l {
  display: block; margin-top: var(--s1);
  color: var(--text-3); font-size: var(--t-small);
  text-transform: uppercase; letter-spacing: .05em; font-weight: 580;
}

.reasons { list-style: none; padding: 0; margin: 0; display: grid; gap: var(--s3); }
.reasons li {
  border: 1px solid var(--border);
  border-left: 3px solid var(--tone, var(--border-strong));
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--surface-1);
  padding: var(--s3) var(--s4);
}
.reason-head { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }
.reason-rule { font-weight: 620; font-size: var(--t-body); }
.reasons p { margin: var(--s2) 0 0; color: var(--text-2); font-size: var(--t-body); }
.reasons-more { margin-top: var(--s3); }

.coverage {
  border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
  border-left: 3px solid var(--warn);
  background: var(--warn-soft);
  padding: var(--s3) var(--s4);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.coverage + .coverage { margin-top: var(--s3); }

/* Findings. Grouped by package, worst first. The severity chip sits in its
   own column so the eye can run down it. */
.fgroup { border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-1); box-shadow: var(--e1); overflow: hidden; }
.fgroup + .fgroup { margin-top: var(--s3); }
.fgroup-head {
  display: flex; align-items: baseline; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3) var(--s4);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.fgroup-head .pkg { font-weight: 640; font-size: var(--t-body); word-break: break-all; }
.fgroup-head .spacer { flex: 1; }

.finding {
  display: grid; grid-template-columns: 84px minmax(0, 1fr);
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--border);
  align-items: start;
}
.finding:last-child { border-bottom: none; }
.finding-title { font-size: var(--t-body); line-height: 1.45; }
.finding-id { color: var(--text-3); font-size: var(--t-small); }
.finding-sub { display: flex; gap: var(--s3); flex-wrap: wrap; margin-top: var(--s1); align-items: baseline; }
.fix   { color: var(--allow); font-size: var(--t-small); font-weight: 580; }
.reach { color: var(--block); font-size: var(--t-small); font-weight: 580; }

/* ============================================================= 8. forms */

label { display: block; font-size: var(--t-small); font-weight: 620; margin-bottom: var(--s1); }
label.check {
  font-weight: 440; display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s2); align-items: start; font-size: var(--t-body);
  margin-bottom: 0;
}
label.check input { margin: 3px 0 0; accent-color: var(--accent); }
label.check .hint { grid-column: 2; margin-top: 2px; }

input[type=text], input[type=email], input[type=number], input[type=search], select {
  width: 100%;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface-1); color: var(--text-1);
  font-size: var(--t-body); font-family: inherit;
}
input:focus-visible, select:focus-visible, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}

/* A settings group. Replaces <fieldset>, whose default legend placement and
   border are hard to align with the rest of the surfaces. */
.group { }
.group + .group { margin-top: var(--s5); }
.group-head { margin-bottom: var(--s3); }
.group-head h3 { margin-bottom: var(--s1); }
.group-body { display: grid; gap: var(--s4); }
.field { display: grid; gap: var(--s1); }
.field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s4); }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  padding: var(--s2) var(--s4);
  border-radius: var(--r-sm);
  border: 1px solid var(--border-strong); background: var(--surface-1); color: var(--text-1);
  font-size: var(--t-body); font-weight: 590; cursor: pointer; font-family: inherit;
  white-space: nowrap;
}
.btn:hover { border-color: var(--accent); text-decoration: none; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.btn-primary:hover { filter: brightness(1.07); }
.btn-danger  { color: var(--block); border-color: color-mix(in srgb, var(--block) 40%, var(--border)); background: transparent; }
.btn-danger:hover { border-color: var(--block); }
.btn-quiet   { background: transparent; border-color: transparent; color: var(--text-3); }
.btn-quiet:hover { color: var(--text-1); background: var(--surface-2); border-color: var(--border); }
.btn-sm      { padding: var(--s1) var(--s3); font-size: var(--t-small); }
.btn-lg      { padding: var(--s3) var(--s5); font-size: var(--t-lead); }

.toolbar {
  display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
  margin-bottom: var(--s4);
}
.toolbar .spacer { flex: 1; }

.seg { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--r-sm); overflow: hidden; }
.seg button {
  border: 0; background: var(--surface-1); color: var(--text-3);
  padding: var(--s2) var(--s3);
  font-size: var(--t-small); font-weight: 570; cursor: pointer; font-family: inherit;
  border-right: 1px solid var(--border);
}
.seg button:last-child { border-right: 0; }
.seg button:hover { color: var(--text-1); background: var(--surface-2); }
.seg button.on { background: var(--accent); color: var(--accent-fg); }
/* The "no filter" option in each group ends in a bare colon - data-filter="verdict:".
   It is selected by default on every page, so painting it in full accent puts
   the loudest thing on screen on the control that is doing nothing. Selected,
   but quietly. */
.seg button[data-filter$=":"].on {
  background: var(--surface-3); color: var(--text-1); font-weight: 620;
}
/* A standalone toggle outside a .seg, e.g. "Has a fix". */
.btn.on { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }

.search { flex: 0 1 300px; min-width: 180px; }
/* A filter with too many values for a row of buttons. Sized to its content
   like a .seg, not stretched to the toolbar like a form field. */
.select-filter { width: auto; max-width: 260px; padding: var(--s2) var(--s3); font-size: var(--t-small); font-weight: 570; }
.inline-label { margin: 0; font-weight: 520; color: var(--text-3); }
.page-head-actions select { width: auto; max-width: 260px; }

textarea {
  width: 100%;
  padding: var(--s2) var(--s3);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface-1); color: var(--text-1);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--t-small); line-height: 1.6; resize: vertical;
}
textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* A number with its unit beside it, so "7" reads as "7 days". */
.field-unit { display: flex; align-items: center; gap: var(--s2); }
.field-unit input { flex: 1; }
.field-unit span { color: var(--text-3); font-size: var(--t-small); white-space: nowrap; }

/* One rule of the policy: what it is about on the left, the Off / Warn /
   Block choice on the right. */
.rule {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s4); align-items: center;
}
.rule-text { display: grid; gap: 2px; font-size: var(--t-body); }
.rule-text strong { font-weight: 600; }

/* Radios drawn as a segment. The input stays in the document for the form
   and for the keyboard; only its box is hidden. Checked state is the
   browser's own, so no script is involved. */
.seg-radio label {
  position: relative; margin: 0; font-weight: 570;
  border-right: 1px solid var(--border);
}
.seg-radio label:last-child { border-right: 0; }
.seg-radio input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  margin: 0; opacity: 0; cursor: pointer;
}
.seg-radio span {
  display: block; padding: var(--s2) var(--s3);
  background: var(--surface-1); color: var(--text-3);
  font-size: var(--t-small); cursor: pointer;
}
.seg-radio label:hover span { color: var(--text-1); background: var(--surface-2); }
.seg-radio input:checked + span { background: var(--surface-3); color: var(--text-1); font-weight: 640; }
.seg-radio input[value="warn"]:checked + span { background: var(--warn-soft); color: var(--warn); }
.seg-radio input[value="block"]:checked + span { background: var(--block-soft); color: var(--block); }
.seg-radio input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: -2px; }

.pager {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-top: var(--s3);
}
.pager-nav { display: flex; align-items: center; gap: var(--s2); }
.pager-size {
  display: flex; align-items: center; gap: var(--s2);
  margin: 0; font-size: var(--t-small); font-weight: 520; color: var(--text-3);
}
.pager-size select { width: auto; padding: var(--s1) var(--s2); font-size: var(--t-small); }
.btn[disabled] { opacity: .4; cursor: default; }
.btn[disabled]:hover { border-color: var(--border-strong); }

.row-form { display: flex; gap: var(--s4); align-items: flex-end; flex-wrap: wrap; }
.row-form > div { flex: 1 1 220px; }

/* ============================================================ 9. blocks */

.alert {
  border-radius: var(--r); padding: var(--s4);
  margin-bottom: var(--s5);
  border: 1px solid var(--border);
  background: var(--surface-1);
}
.alert-good { background: var(--allow-soft); border-color: color-mix(in srgb, var(--allow) 30%, transparent); }
.alert-bad  { background: var(--block-soft); border-color: color-mix(in srgb, var(--block) 30%, transparent); }
.alert-warn { background: var(--warn-soft);  border-color: color-mix(in srgb, var(--warn) 30%, transparent); }
.alert strong { display: block; margin-bottom: var(--s1); }

/* Grid, not flex. With flex the command box collapsed to a sliver and the
   Copy button took the row: a nowrap <pre> as a flex item has an intrinsic
   min-width that fights flex-shrink, and the interaction is subtle enough
   that it was wrong twice. `1fr auto` says what is meant with no ambiguity -
   the command takes the space, the button takes its label. */
.keybox {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s2); align-items: stretch;
  margin: var(--s3) 0;
}
.key {
  min-width: 0;
  background: var(--text-1); color: var(--surface-0);
  padding: var(--s3); border-radius: var(--r-sm);
  overflow-x: auto; margin: 0; user-select: all; white-space: nowrap;
  line-height: 1.6;
}
.keybox .btn { white-space: nowrap; }

/* The install command is the page's primary call to action, so it wraps
   rather than hiding half of itself behind a scrollbar. An API key stays
   nowrap: it is one token, and wrapping it invites a broken copy-paste.

   This class already existed and the landing page did not use it - it set
   `style="white-space:pre-wrap"` instead, which the CSP dropped, so four
   example commands written on four lines rendered as one scrolling line. */
.key-wrap { white-space: pre-wrap; overflow-x: visible; word-break: break-word; }

/* =========================================================== 10. public */

/* The landing page. Signed out, this is the first thing anyone sees, so it
   gets a real page width rather than the 400px auth column it used to
   borrow. */
.public .wrap { max-width: 960px; margin: 0 auto; padding-top: var(--s8); }

.hero { text-align: center; margin-bottom: var(--s7); }
.hero-mark { display: inline-flex; align-items: center; gap: var(--s3); }
.hero h1 { font-size: var(--t-display); letter-spacing: -.025em; }
.hero .lede {
  margin: var(--s4) auto 0; max-width: 58ch;
  font-size: var(--t-lead); color: var(--text-2);
}

.steps { list-style: none; counter-reset: step; padding: 0; margin: 0; }
.steps li + li { margin-top: var(--s3); }
/* The marker is positioned, not a grid or flex item.
   A `display: grid` list item makes each CHILD ELEMENT its own grid item, so
   the <a> and <code> inside the step text were torn onto their own rows in
   the 22px marker column. Ordinary inline flow is what prose needs; only the
   marker is taken out of it. */
.steps li {
  counter-increment: step;
  position: relative;
  padding-left: 34px;
  min-height: 22px;
  font-size: var(--t-body);
}
.steps li::before {
  content: counter(step);
  position: absolute; left: 0; top: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px;
  border-radius: var(--r-pill);
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--text-3); font-size: var(--t-tiny); font-weight: 660;
}

/* Exit codes read as a legend, not as prose. */
.codes { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s2); }
.code-chip {
  border: 1px solid var(--border); border-left: 3px solid var(--tone, var(--border-strong));
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background: var(--surface-1);
  padding: var(--s2) var(--s3);
}
.code-n { display: block; font-size: var(--t-h2); font-weight: 660; line-height: 1.1; color: var(--tone, var(--text-1)); font-variant-numeric: tabular-nums; }
.code-l { display: block; margin-top: 2px; color: var(--text-3); font-size: var(--t-small); text-transform: uppercase; letter-spacing: .05em; font-weight: 580; }

.auth { max-width: 400px; margin: 0 auto; }
.public .wrap.wrap-auth { max-width: 400px; padding-top: 10vh; }
.auth-head { text-align: center; margin-bottom: var(--s5); }
.auth-head h1 { font-size: var(--t-h1); display: inline-flex; align-items: center; gap: var(--s2); }
.auth-head .lede { margin-top: var(--s2); }
.auth form.card { display: grid; gap: var(--s3); }
.auth .btn-primary { width: 100%; }

/* ======================================================= 11. responsive */

@media (max-width: 1100px) {
  .split { grid-template-columns: minmax(0, 1fr); }
  .split-aside { position: static; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  /* The rail becomes a top bar. It keeps the same markup and the same
     classes; only the axis changes. */
  body.app { grid-template-columns: minmax(0, 1fr); }
  .rail {
    height: auto; flex-direction: row; align-items: center;
    gap: var(--s3); padding: var(--s2) var(--s4);
    border-right: 0; border-bottom: 1px solid var(--border);
  }
  .rail-brand { padding: 0; }
  .rail nav { flex-direction: row; flex: 1; overflow-x: auto; gap: var(--s1); }
  .rail nav a { padding: var(--s2) var(--s3); white-space: nowrap; }
  .rail nav svg { display: none; }
  .rail-foot { margin: 0; padding: 0; border-top: 0; flex: none; }
  .who { display: none; }

  .cols, .cols-even { grid-template-columns: minmax(0, 1fr); }
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .wrap { padding: var(--s5) var(--s4) var(--s6); }
  .foot { padding: 0 var(--s4) var(--s5); }
  .public .wrap { padding-top: var(--s6); }
}

@media (max-width: 620px) {
  .split-aside { grid-template-columns: minmax(0, 1fr); }
  .verdict-band { grid-template-columns: minmax(0, 1fr); padding: var(--s4); }
  .risk { text-align: left; padding: var(--s4) 0 0; border-left: 0; border-top: 1px solid color-mix(in srgb, var(--tone) 22%, transparent); }
  .finding { grid-template-columns: minmax(0, 1fr); gap: var(--s1); }
  .field-row { grid-template-columns: minmax(0, 1fr); }
  .rule { grid-template-columns: minmax(0, 1fr); }
  .codes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page-head { padding-bottom: var(--s4); margin-bottom: var(--s4); }
}

@media (max-width: 520px) {
  .keybox { grid-template-columns: minmax(0, 1fr); }
  .keybox .btn { justify-self: start; }
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
