/* Dense before decorative. This is a spreadsheet that happens to be served
   over HTTP: the table gets the screen, everything else gets out of the way.
   No webfonts, no framework, and no script except the 40 lines in app.js that
   remember whether the overview panel is folded — every other piece of state
   on this site is a GET parameter. Switching the overview's split is done with
   a radio button and the `:checked ~` selectors below, not with JavaScript. */

/* The neutrals are Waratek's (waratek/styleguide, --wk-ink / --wk-muted /
   --wk-border / --wk-gray / --wk-zebra), which are warm and faintly purple
   where this page's originals were cool and faintly blue. Nothing about the
   layout moved to get them: the brand arrives as a palette and a mark in the
   header, not as the styleguide's geometry — its 17px/1.7 body, 76px section
   rhythm, 12px radii and gradient-with-glow buttons all belong to a marketing
   page and would cost this one most of its rows.

   The brand is NOT the interaction colour. This page's whole signal
   vocabulary below is red-as-severity — Critical, KEV, error — and painting
   every link and button in Waratek's crimson would sit a pinkish red meaning
   "clickable" beside a brownish red meaning "this one is exploited in the
   wild". So --accent stays blue, and the brand is confined to the one strip
   that carries no data at all: the dark header. */
:root {
  --font: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;

  --ink: #1e2230;
  --dim: #5a5766;
  --line: #e4e4e8;
  --rule: #ededf1;
  --bg: #fff;
  --panel: #f2f2f2;
  --zebra: #fafafb;
  /* One step below --dim, for text that must read as fainter than a value: a
     placeholder, a zero count, a rule that did not match. This was three
     steps, inherited from four hand-picked greys that differed by 4% and were
     never told apart on screen — they just made the page look like it had no
     opinion about what mattered. Three text weights is the whole ramp: --ink
     is a value, --dim is a label, --faint is an absence. */
  --faint: #9a97a6;
  /* Control borders are a separate token from hairlines because they are held
     to a different bar: --line is a 1.3:1 rule between rows, which is all a
     rule needs to be, but the edge of a text box is a UI component and wants
     3:1. --wk-border-strong clears it; --line never did. */
  --control: #8a8796;

  /* Two measures, and only two. The CVE page had four — 1200px of structure,
     900px of strip, 90ch of reasoning, 74ch of lede — stacked vertically, so
     the right edge stepped in and out four times on the way down and nothing
     lined up with anything. Structure is full width; anything read as
     sentences gets the reading measure. Nothing gets a third. */
  --w-page: 1000px;
  --w-prose: 90ch;

  --accent: #0b4f9e;
  /* Chrome, in the styleguide's --wk-dark. The point is not decoration: a
     near-black bar states that the header is not part of the data. Every
     colour below it is a claim about a CVE, and the one strip that is only
     navigation now says so without spending a hue on it. */
  --chrome: #171d24;
  --chrome-line: rgba(255, 255, 255, .12);
  --on-chrome: rgba(255, 255, 255, .72);
  --on-chrome-dim: rgba(255, 255, 255, .55);
  /* Coral, not the crimson --wk-crimson that is Waratek's primary: the brand
     only ever appears on the dark bar now, and crimson is 2.4:1 there. The
     styleguide hits the same wall and carries dark grounds in coral, so this
     is one token rather than a pair with the crimson permanently unused. */
  --brand: #ff887c;
  --warn: #8a5a00;
  --warnbg: #fff6e0;
  --err: #96231d;
  --errbg: #fdeceb;
}

* { box-sizing: border-box; }

/* The page is a fixed frame with one scrolling pane in it, the way a
   spreadsheet is: the filters and the result count stay put and the rows move
   under them. It also fixes the header: `overflow-x: auto` on a wrapper makes
   that wrapper the containing block for `position: sticky`, so a thead offset
   from the viewport top would be pushed down over the first row instead. With
   the wrapper scrolling both axes, the header sticks to its own top at 0. */
body {
  margin: 0;
  color: var(--ink);
  background: var(--bg);
  font: 13px/1.45 var(--font);
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}
.bar, .filters, .note, .resultbar, .ins { flex: none; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
code { font-family: var(--mono); }

/* There was no focus-visible rule at all, so the whole filter bar was
   keyboard-navigable and invisible while you did it. One rule covers every
   control because they are all the same size here; `:focus-visible` rather
   than `:focus` keeps it off the pointer path. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: 2px;
}

/* ---------- header ---------- */

.bar {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 8px 14px;
  border-bottom: 1px solid var(--chrome-line);
  background: var(--chrome);
  color: var(--on-chrome);
}
/* The accent is a blue picked to be read on white and is 2.8:1 here, so the
   focus ring inside the bar switches to the colour that survives the ground.
   Same rule, different ink — not a second focus convention. */
.bar a:focus-visible { outline-color: var(--brand); }
/* The mark sits at the cap height of the wordmark beside it and inside the
   bar's existing 8px padding, so branding the header costs no vertical space
   — the row was already taller than 16px. */
.brand { display: flex; align-items: center; gap: 7px; font-weight: 700; letter-spacing: .06em; color: var(--brand); }
.brand img { display: block; width: 16px; height: 16px; }
.bar nav { display: flex; gap: 12px; }
.bar nav a { color: var(--on-chrome-dim); }
.bar nav a:hover { color: var(--on-chrome); }
/* An inset shadow rather than a border: a 2px underline on the active item
   would make that one anchor taller than its neighbours and shift the bar.
   The marker is permanent, so the global `a:hover` underline is suppressed
   here — the two stack into a double rule, and hover has nothing to add to
   the item you are already on. */
.bar nav a.on { color: #fff; font-weight: 600; box-shadow: inset 0 -2px 0 var(--brand); }
.bar nav a.on:hover { text-decoration: none; }
.corpus { margin-left: auto; color: var(--on-chrome-dim); font-variant-numeric: tabular-nums; }

/* ---------- filters ---------- */

/* Four controls on one line, the rest folded away. The bar costs ~115px where
   it used to cost ~330px, and the difference goes to rows.

   Folding is only safe because a set-but-hidden filter still announces itself
   as a chip, and because a closed <details> still submits the inputs inside
   it — nothing behind the fold is dropped from the query. */
.filters {
  padding: 8px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
  /* Capped so the table is reachable even with both panels open: the filters
     are the means, the rows are the point. */
  max-height: 46vh;
  overflow-y: auto;
}
.row { display: flex; gap: 14px; margin-bottom: 8px; }
.row.wrap { flex-wrap: wrap; gap: 10px 14px; }
.row.primary { align-items: flex-start; flex-wrap: wrap; gap: 8px 12px; margin-bottom: 6px; }
label { display: flex; flex-direction: column; gap: 2px; }
/* Capped rather than left to take every pixel of slack: a 780px text box for a
   substring reads as the page's main event, which it is not. The leftover width
   goes to the button, pushed right by the auto margin. */
label.grow { flex: 1 1 240px; max-width: 420px; }
label.sm { width: 108px; }
label > span { color: var(--dim); font-size: 11px; }
label .sub, .actions .sub { color: var(--dim); font-size: 11px; }

/* The three multiselects that stay on screen. Four visible rows rather than
   five: the fifth was never the difference between finding a status and not,
   and it is 18px off the top of every page. */
label.multi { width: 150px; }
label.multi.wide { width: 210px; }
label.scope { width: 132px; }
.row.primary .go { display: flex; gap: 8px; align-self: flex-end; margin-left: auto; padding-bottom: 1px; }

input[type=text], input[type=date], select, textarea, button, .btn {
  font: inherit;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--control);
  border-radius: 3px;
  padding: 3px 5px;
}
textarea { font-family: var(--mono); font-size: 12px; resize: vertical; }
select[multiple] { padding: 0; }

/* A placeholder that renders as dark as a value makes an empty filter look
   like a set one, which on this page is the difference between "all CVEs" and
   "CVEs about deserialization". */
::placeholder { color: var(--faint); opacity: 1; }
select[multiple] option { padding: 1px 5px; }

.actions { display: flex; align-items: center; gap: 10px; }
button, .btn { cursor: pointer; background: #fff; padding: 4px 12px; }
button { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }

/* ---------- the fold ---------- */

/* Collapsed, the panels are two summary lines sharing a row with the chips.
   Opened, one takes the full width and wraps the rest below it — which is what
   `flex: 1 0 100%` buys over stacking them, since two open panels side by side
   would be two narrow columns of nothing. No `order`: reordering would move the
   summary out from under the pointer that just opened it. */
.panels { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 16px; }
.panels > details { flex: none; }
.panels > details[open] { flex: 1 0 100%; }
.panels summary {
  cursor: pointer;
  color: var(--dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  white-space: nowrap;
}
.panels summary b { color: var(--accent); }

.fieldgrid { display: flex; flex-wrap: wrap; gap: 8px 14px; padding: 6px 0 0; }
.fieldgrid label { width: 150px; }
.fieldgrid.sm label { width: 112px; }
/* The release names run to "Oracle Critical Security Patch Update August 2026",
   which is wider than any other filter on the page. */
.fieldgrid label.adv { width: 250px; }
/* The exploitation select's options are sentences ("Working exploit
   published"), so it is wider than the multiselects it sits beside. */
.row.primary label.ev { width: 190px; }
.fieldgrid label.adv a { font-size: 11px; }

.colgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 2px 12px; padding: 6px 0; }
label.chk { flex-direction: row; align-items: center; gap: 5px; font-size: 12px; }

/* ---------- chips ---------- */

/* What the fold is hiding, said out loud. A request that arrives from a link —
   every count on the advisories page is one — would otherwise render as an
   ordinary listing over a quietly narrowed corpus. */
.chips { display: flex; flex-wrap: wrap; gap: 4px 6px; }
/* A hidden filter, or a CWE, named in the smallest shape that still reads as
   one object: a hairline box at the page's own corner radius. It was a filled
   capsule, which put the loudest shape available around a value nobody came
   to the page for — the chip is a footnote about the query, not a finding. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 5px;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--panel);
  color: var(--accent);
  font-size: 11px;
  font-weight: 600;
  white-space: nowrap;
}
.chip a { color: var(--faint); font-size: 10px; font-weight: 400; text-decoration: none; }
.chip a:hover { color: var(--err); text-decoration: none; }

/* ---------- the lookup page ---------- */

/* One field, so it gets the room the table's filter bar refuses it — including
   the width cap that keeps a substring box from dominating the table's row. */
.filters.lookup { max-height: none; padding: 14px; }
.filters.lookup label.grow { flex: none; max-width: 820px; }
.filters.lookup textarea { width: 100%; }
.filters.lookup .sub { margin-top: 4px; }

/* ---------- notes ---------- */

.note { margin: 8px 14px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 3px; background: var(--panel); }
.note.warn { border-color: #e6cf95; background: var(--warnbg); color: var(--warn); }
.note.err { border-color: #e8b6b2; background: var(--errbg); color: var(--err); }
.note.miss summary { cursor: pointer; font-weight: 600; }
.note textarea { width: 100%; margin-top: 6px; }
.note .schema { color: var(--dim); font-size: 12px; margin: 6px 0 0; }

/* ---------- result bar ---------- */

.resultbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 6px 14px;
  border-bottom: 1px solid var(--line);
  background: #fff;
}
.resultbar.bottom { border-bottom: 0; border-top: 1px solid var(--line); }
.count { font-weight: 600; font-variant-numeric: tabular-nums; }
.exports { color: var(--dim); }
.exports a { margin-right: 10px; }
.pager { margin-left: auto; display: flex; align-items: center; gap: 10px; font-variant-numeric: tabular-nums; }
.pager .off { color: var(--faint); }

/* ---------- the table ---------- */

.tablewrap { flex: 1; min-height: 0; overflow: auto; }

table.grid {
  border-collapse: collapse;
  width: 100%;
  table-layout: auto;
}
table.grid th, table.grid td {
  border-bottom: 1px solid var(--rule);
  padding: 3px 8px;
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
}
table.grid thead th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--dim);
  white-space: nowrap;
}
table.grid thead th a { color: inherit; }
.sortmark { display: inline-block; width: .8em; color: var(--accent); }
table.grid tbody tr:nth-child(even) { background: var(--zebra); }
table.grid tbody tr:hover { background: #eef4fb; }

/* Numbers line up under each other or they are not worth reading.

   Qualified with the table, and not just `td.num`: the cell rule above is
   `table.grid td`, which at (0,1,2) outranks a bare `td.num` at (0,1,1), so
   the left alignment won and every numeric column read as text. */
table.grid td.num, table.grid th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* One column is allowed to take the slack and be clipped; the full text is in
   the cell's title, and the export carries it whole. */
td.wide, th.wide { white-space: nowrap; max-width: 60ch; overflow: hidden; text-overflow: ellipsis; }

/* The identifier and the band are one statement — which one, and how bad —
   and are read as a pair. `table-layout: auto` over a 100%-wide table shares
   the leftover width among the columns, which left the capsule floating half
   a column away from the ID it qualifies; `width: 1%` is the standing trick
   for "give this column its content width and hand the slack to the wide
   one". Dropping the left padding closes the seam to the 8px the ID's own
   cell contributes, so the pair sits closer to each other than either does
   to anything else in the row. */
table.grid th.band, table.grid td.band { width: 1%; padding-left: 0; }

td.empty { color: var(--dim); text-align: center; padding: 24px; }

/* ---------- advisories ---------- */

table.advisories td.name a { font-weight: 600; }
table.advisories .ext { color: var(--dim); margin-left: 4px; }
table.advisories .totals { background: var(--panel); font-weight: 600; }
table.advisories .totals td { border-bottom: 1px solid var(--line); }
table.advisories .dim { color: var(--dim); font-weight: 400; }
table.advisories th.sev, table.advisories td.sev { text-align: left; }
table.advisories a.kev { color: var(--err); }

/* One bar per release, sized in percentages by the server. It is drawn from
   links so a band is also a filter, which is why the segments are anchors
   rather than a table or an SVG.

   `sevbar`, not `bar`: the page header owns that class, and its padding, gap
   and border-bottom are not overridden by a more specific selector setting
   only display and size — the segments end up in a zero-height content box. */
.sevbar {
  display: inline-flex;
  width: 180px;
  height: 9px;
  vertical-align: middle;
  border-radius: 2px;
  overflow: hidden;
  background: var(--rule);
}
.sevbar .seg { display: block; height: 100%; }
.sevbar .seg:hover { opacity: .75; }

/* The counts beside the bar are a small table of their own: one fixed slot per
   band, right-aligned, kept even when a release has none of that band. Left to
   flow, the numbers ragged out — a release with no Low CVEs shifted every
   count to its left, so no column could be read down the page. The header's
   key uses the same slots, which is what puts C/H/M/L/U over their numbers. */
.sev .legend, .sevkey { display: inline-flex; margin-left: 8px; font-variant-numeric: tabular-nums; }
.sevhead { display: inline-block; width: 180px; }
.sev .slot { width: 4.2em; text-align: right; }

/* ---------- the colour vocabulary ---------- */

/* Every band class defines two custom properties and nothing else: --c is the
   band's colour as an area (a bar segment, a legend swatch, an SVG fill) and
   --t is the same band as text, darkened where the area colour would be too
   pale to read. Declaring the colour once rather than once per place it is
   drawn is what stops the advisories bar and the overview chart from slowly
   coming to disagree about what "High" looks like. */
.sevbar .seg, .sw { background: var(--c); }
.slot { color: var(--t); }
.timeline polygon { fill: var(--c); }

/* The one pill in the interface, written once so it stays that way.
   A tinted capsule is spent on a single value — a CVSS score with its band —
   because that value is a pair: the number is unreadable without the word and
   the word unactionable without the number. Everything else the vocabulary
   above paints is one word, and one word is said as a word.

   The tint is derived from --c rather than written out, so a pill cannot come
   to disagree with the bar, the legend or the chart drawn from the same band;
   it is mixed with `transparent` rather than white so it composites correctly
   over a zebra stripe and a row hover. */
.pill {
  border-radius: 999px;
  padding: 1px 8px;
  background: color-mix(in srgb, var(--c) 13%, transparent);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The severity ramp is one hue darkening into red, so the bar reads as a
   gradient of seriousness rather than as five unrelated categories. Critical
   and High carry the saturation — those are the two bands worth a reader's
   attention — and Medium/Low are pulled toward grey rather than continuing
   the ramp at full strength, so they recede instead of competing with them. */
.sev-Critical { --c: #96231d; --t: #96231d; }
.sev-High     { --c: #cc5f22; --t: #b4541e; }
.sev-Medium   { --c: #a89464; --t: #7d6f4f; }
.sev-Low      { --c: #8a9a80; --t: #66755d; }
/* None is a real upstream assessment (scored 0.0) and Unknown is the absence of
   one, so they are not the same grey: None reads as a decided nil, Unknown as
   an empty slot. Both are already muted; they don't need pulling further. */
.sev-None     { --c: #8d949b; --t: #6b7278; }
.sev-Unknown  { --c: #ccd1d6; --t: #8d949b; }

/* Status is not a ramp, because the eight values are not one scale: the two
   MITIGATED-BY-* statuses are protection claims and share a green family, and
   the three exclusions are greys precisely because they are not work. RASP
   takes the strong green — it is the whole claim now that patch rule, secure
   rule and the legacy enterprise agent are one status told apart by mechanism
   — and environment stays paler, because it says the agent never had to do
   anything rather than that it did. */
.st-rasp          { --c: #1f7a4d; --t: #1f7a4d; }
.st-indev         { --c: #6fa88f; --t: #4a7d67; }
.st-env           { --c: #74bb9a; --t: #3f7a5f; }
.st-candidate     { --c: #2b6cb0; --t: #2b6cb0; }
.st-queued        { --c: #d9a520; --t: #8a6a10; }
.st-noexploit     { --c: #7f93a8; --t: #5f7386; }
.st-nofix         { --c: #9aa8b6; --t: #6d7d8c; }
.st-outofscope    { --c: #aeb6bd; --t: #79828a; }
.st-notapplicable { --c: #c7ced4; --t: #98a0a7; }
.st-rejected      { --c: #dee3e7; --t: #a9b0b6; }

/* Protection is green where ARMR blocks it, neutral where it does not — "not
   blocked" is the ordinary case for most of the corpus and painting it red
   would make every view look like an emergency — and amber where VRT has not
   established either way, which is the one that wants a person. */
.pr-full    { --c: #2f9c63; --t: #27795b; }
.pr-none    { --c: #8d949b; --t: #6b7278; }
.pr-unknown { --c: #d9a520; --t: #8a6a10; }
/* Nothing the pipeline emits today lands here; the band exists so that a
   protection value this list has not heard of is visible rather than dropped
   out of a total that has to add up. */
.pr-unrecorded { --c: #e6e9ec; --t: #a9b0b6; }

/* Exploit maturity, ranked. The top three ride the severity ramp deliberately:
   somebody publishing a working exploit is the same kind of alarm a Critical
   is, and the ramp cools as the artifact gets weaker.

   `predicted` breaks out of the ramp on purpose. It is not a weaker shade of
   observed evidence, it is a different kind of claim — a model's forecast where
   nothing has been published — and painting it a paler orange would say it sits
   just below a proof of concept on the same scale. Steel says "different
   question". `unreported` stays the neutral grey every absence uses. */
.ev-attacked         { --c: #96231d; --t: #96231d; }
.ev-weaponized       { --c: #cc5f22; --t: #b4541e; }
.ev-proof-of-concept { --c: #d9a520; --t: #8a6a10; }
.ev-predicted        { --c: #7f93a8; --t: #5f7386; }
.ev-unreported       { --c: #c7ced4; --t: #98a0a7; }

/* ---------- the overview panel ---------- */

/* What the filtered set is made of, between the filters and the rows. It costs
   ~160px open and ~22px folded, and the fold is remembered per person in
   localStorage rather than in the URL — see static/app.js. */
.ins {
  padding: 4px 14px 6px;
  border-bottom: 1px solid var(--line);
  background: #fff;
}
.ins > summary {
  cursor: pointer;
  color: var(--dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  display: flex;
  gap: 12px;
  align-items: baseline;
}
.ins .institle { font-weight: 600; }
/* Folded, the summary carries the numbers itself, so collapsing the panel
   costs the headline figures rather than hiding them. */
.ins .insglance {
  text-transform: none;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ins[open] .insglance { display: none; }

.tiles { display: flex; flex-wrap: wrap; margin: 4px 0 2px; }
.tile {
  display: flex;
  align-items: baseline;
  gap: 5px;
  padding: 1px 12px 1px 0;
  margin-right: 12px;
  border-right: 1px solid var(--rule);
  color: var(--ink);
}
.tile:last-child { border-right: 0; }
.tile:hover { text-decoration: none; }
.tile:hover b { color: var(--accent); }
.tile b { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
.tile span { color: var(--dim); font-size: 11px; }
.tile i { color: var(--faint); font-size: 10px; font-style: normal; font-variant-numeric: tabular-nums; }
.tile.total b { font-size: 16px; }
/* A counter that came out equal to the total is folded into it as words, so
   the strip stays one shape — every tile a number and a label. It carries the
   counter's own tooltip, which is the only thing the fold would otherwise
   lose. */
.tile.total em { font-style: normal; }
/* A counter with no rows behind it is not a link — see drillURL — so it is
   drawn as what it is rather than as a link that leads nowhere useful. */
.tile.zero, .tile.zero b { color: var(--faint); }
.tile.zero i { color: var(--faint); }

/* The four splits are all in the DOM and only one is displayed, switched by a
   radio button. That is what makes changing the split instant and free: the
   single aggregate behind the panel already counted every facet, so a request
   would be spent re-fetching numbers the page is holding. */
.splitradio { position: absolute; opacity: 0; pointer-events: none; }
.ins .pane { display: none; }
#sp-severity:checked   ~ .panes .pane-severity,
#sp-status:checked     ~ .panes .pane-status,
#sp-protection:checked ~ .panes .pane-protection,
#sp-evidence:checked   ~ .panes .pane-evidence { display: block; }

.panes { display: flex; gap: 18px; align-items: flex-start; }
.legendcol { flex: none; width: 310px; max-height: 128px; overflow-y: auto; }
.chartcol { flex: 1; min-width: 0; }

.lrow {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 3px;
  font-size: 11px;
  line-height: 1.18;
  color: var(--ink);
}
.lrow:hover { background: #eef4fb; text-decoration: none; }
.lrow.zero { color: var(--faint); }
.lrow.zero .sw { opacity: .35; }
.sw { flex: none; width: 9px; height: 9px; border-radius: 2px; }
.lname { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lnum, .lpct { font-variant-numeric: tabular-nums; text-align: right; }
.lnum { width: 5.4em; }
.lpct { width: 3.6em; color: var(--dim); }

.charthead { display: flex; align-items: baseline; gap: 10px; font-size: 11px; color: var(--dim); }
.splitpick { display: flex; align-items: baseline; gap: 2px; }
.splitpick label { cursor: pointer; padding: 0 6px; border-radius: 3px; color: var(--accent); }
.splitpick label:hover { background: #eef4fb; }
#sp-severity:checked   ~ .panes label[for="sp-severity"],
#sp-status:checked     ~ .panes label[for="sp-status"],
#sp-protection:checked ~ .panes label[for="sp-protection"],
#sp-evidence:checked   ~ .panes label[for="sp-evidence"] {
  background: var(--accent);
  color: #fff;
}
.charthead .yaxis { margin-left: auto; font-variant-numeric: tabular-nums; }

/* Stretched to whatever width is left over, which is harmless because the
   chart carries no text and no strokes: every label belongs to the HTML
   around it. */
/* White rather than the panel grey, because the palest bands are greys — "no
   evidence found" is 90% of the default view — and a light grey area on a
   light grey ground is a chart of nothing. The hairline says where the plot
   ends without competing with what is in it. */
.timeline {
  display: block;
  width: 100%;
  height: 96px;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--rule);
  border-radius: 2px;
}
.timeline rect.hot { fill: transparent; }
.timeline a:hover rect.hot { fill: rgba(11, 79, 158, .12); }

.axis { position: relative; height: 13px; font-size: 10px; color: var(--dim); font-variant-numeric: tabular-nums; }
.axis span { position: absolute; top: 1px; transform: translateX(-50%); white-space: nowrap; }
.nochart { margin: 6px 0; color: var(--dim); }

/* ---------- the products page ---------- */

/* The body is a fixed frame with one scrolling pane in it, so this page
   supplies its own — ninety families is more than a screen. */
.pagewrap { flex: 1; min-height: 0; overflow: auto; padding: 0 14px 24px; }
.pagewrap h1 { font-size: 18px; margin: 16px 0 4px; }
.pagewrap h2 { font-size: 14px; margin: 22px 0 4px; }
.lede { color: var(--dim); margin: 0 0 12px; max-width: var(--w-prose); }

.askform {
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--panel);
  padding: 10px 12px;
  margin-bottom: 8px;
  max-width: 900px;
}
.askform .row { margin-bottom: 0; align-items: flex-end; }
.askform label.grow { flex: 1; min-width: 220px; }
.askform label.ver { width: 150px; }
.askform select, .askform input { width: 100%; }
.askform .go { flex: none; }

.pagewrap .note { margin: 12px 0 0; max-width: var(--w-prose); }

/* One row per family, dense enough that the list reads as a list rather than
   as ninety cards. The count is right-aligned so the column scans. */
.family { border-bottom: 1px solid var(--rule); max-width: 900px; }
.family > summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 5px 2px;
  cursor: pointer;
  list-style: none;
}
.family > summary::-webkit-details-marker { display: none; }
.family > summary::before { content: "▸"; color: var(--dim); font-size: 10px; width: 10px; }
.family[open] > summary::before { content: "▾"; }
.family .fam { font-weight: 600; }
.family .count { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--dim); }
.family .sub { color: var(--dim); font-size: 11px; width: 9em; text-align: right; }

.components { list-style: none; margin: 0 0 8px; padding: 0 0 0 22px; }
.components li { display: flex; align-items: baseline; gap: 10px; padding: 2px 0; }
.components .count { margin-left: auto; font-variant-numeric: tabular-nums; color: var(--dim); }

/* ---------- the CVE page ---------- */

/* One column, full width, and the same grid table the listing uses. The page
   is long by design: it is the whole of what the artifact holds about one
   record, and folding the middle of it would only mean the reader has to guess
   which section the answer is in. The ARMR rule files are folded because a
   rule file is a program and nobody scrolls past one twice, and the decision
   trail is folded because it is the longest table on the page (up to 21 rows)
   and the position it audits is already stated above it. */

/* Capped and centered so prose lines and wide-but-not-huge tables (the rules
   trail, the affected-product list) stay within a reading-width column rather
   than stretching edge-to-edge on a wide monitor.

   This is a child of `.pagewrap`, not the same element as it. Putting both on
   one div was tempting — it saved a wrapper — but it made the scrolling box
   itself 1200px wide and centered, so the page's scrollbar was drawn down the
   middle of a wide window instead of at its edge. The scroll pane has to be
   full-bleed; only what it contains is capped. */
.cve { max-width: var(--w-page); margin: 0 auto; }
/* The public pages mark a section with an eyebrow — tracked uppercase, small,
   quiet — above the thing it names, rather than with a big heading. Dense
   version: the eyebrow *is* the heading, keeping the rule under it so the
   section boundary is still a line and not just a change of weight. It reads
   like the site and costs ~5px a section, of which there are eight. This page
   already speaks in tracked uppercase — the table headers and every panel
   summary do — so it is the existing idiom, not a new one. */
.cve h2 {
  margin: 22px 0 4px;
  padding-bottom: 3px;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--dim);
}
.cve h3 { font-size: 13px; margin: 18px 0 4px; }
.cve section { max-width: 100%; }
.cve p.desc { max-width: var(--w-prose); margin: 0 0 8px; }

/* The trail's h2 doubles as the disclosure trigger, so the collapsed page
   still reads as a normal section heading rather than a mystery toggle. */
.trail summary { cursor: pointer; list-style: none; }
.trail summary::-webkit-details-marker { display: none; }
.trail summary h2::before { content: "▸ "; color: var(--dim); }
.trail details[open] summary h2::before { content: "▾ "; }

/* The header is one line of identity: what this is, how bad upstream says it
   is, what we say about it, and the way out to the other databases. */
.cvehead {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  /* Wider than the 10px the pills sat at: the capsules used to do the
     separating, and plain words at that gap read as one phrase. */
  gap: 14px;
  margin: 14px 0 0;
  padding-bottom: 8px;
}
.cvehead h1 { margin: 0; font-size: 20px; }

/* CISA's own KEV title, when the record has one — a subtitle to the ID, not
   another value on the header row, so it rides underneath rather than
   crowding the pill/status line. */

/* The header row is the CVSS pill and status. Exploit maturity and scope were
   tinted capsules too, and six capsules on one line made every value equally
   loud, which is the same as making none of them findable — the shape was
   carrying nothing the colour was not. Exploit maturity and scope are said
   elsewhere on the page, so the header no longer repeats them. */
.cvehead .slot {
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
}

/* A status or a band as a word, tinted by the vocabulary the rest of the site
   uses. `slot` already paints text with --t; these give it the shape of a
   label so a value and a sentence about it are visibly different things. */
.cve .slot { font-weight: 600; }
.cve p.verdict { margin: 4px 0 6px; font-size: 15px; }
.cve p.verdict a { color: inherit; }

/* The tables carry sentences, which the listing's tables never do — every
   number there is a number. So the prose column wraps and everything else
   keeps the listing's single line. */
/* `width: 100%` on a table is a floor, not a ceiling: a table whose
   max-content width exceeds it simply grows. The decision trail wants 1296px
   (every other table here wants under 1000), so opening it widened the page
   under the reader — the one thing a page this long must not do. `max-width`
   makes the cap binding, and because `.say` is the only column that wraps,
   it is the column that absorbs the difference; the min-content width is
   851px, so this never squeezes the columns that must stay on one line. */
.cve table.grid { margin: 6px 0 0; max-width: 100%; }
.cve table.grid td.say, .cve table.grid th.say { white-space: normal; max-width: var(--w-prose); }
.cve table.grid tbody th { white-space: nowrap; font-weight: 600; color: var(--dim); }
.cve table.grid td.state { font-weight: 600; }

/* The four axes, and the four exploitation sources, as one shape: a strip of
   fixed slots in a fixed order. Both are "four questions, four answers", so
   they are drawn the same way and a reader learns the shape once — glance at
   the same four positions on any CVE page and you have the position and the
   evidence for it.

   It replaced a four-row table whose third column glossed its second. Those
   glosses define the vocabulary, so they were the same four sentences on
   every page in the corpus, and they crowded out the two facts that actually
   vary per CVE — which mechanism, on what basis. Those are the qualifier
   line; the definitions are on `title`. Cost: ~3 lines where the table spent
   ~9, and the section now fits above the fold with the decision under it. */
.axstrip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
  /* The 1px grid gap over a --line ground draws the dividers, so the cells
     need no borders of their own and can't double up at the seams. */
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow: hidden;
}
.axstrip > li { background: #fff; padding: 4px 10px 5px; min-width: 0; }
.axk {
  display: block;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* `.slot` paints this from --t where the value has a band; where it has none
   the custom property is unset, the declaration is invalid at computed-value
   time, and it simply inherits --ink. That is the intended fallback. */
.axstrip .axv { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }
.axstrip .axq { display: block; font-size: 11px; color: var(--dim); line-height: 1.35; }
/* An empty qualifier still holds its line, so the four values stay on one
   baseline and the strip is the same height whatever a given CVE carries. */
.axstrip .axq:empty::before { content: "\200b"; }
/* A source that found nothing recedes; one that found something does not. */
.axstrip > li.no .axv { color: var(--faint); font-weight: 400; }

.cve p.reason { margin: 10px 0 0; max-width: var(--w-prose); }
/* The verdict's own meaning, which is the sentence the page exists to say. It
   used `.lede` and was therefore grey — but `.lede` is for section subtitles,
   the explanatory copy a reader skips on their second visit. This is content,
   so it is ink. The distinction is the rule that keeps the grey honest:
   --dim labels the page, it does not speak for it. */
.cve p.says { margin: 0 0 10px; max-width: var(--w-prose); }
/* A label column sized to its labels. Left to `table-layout: auto` the two
   words "Decided by" claimed 190px and pushed every value out of line with
   the strip directly above. */
.cve table.decision tbody th { width: 9em; }
/* A value riding a heading. The heading is the label and stays --dim; the
   tally is the answer and takes its band colour, so the pair reads the way
   every other label/value on the page does. */
.cve h2 .tally { margin-left: 10px; color: var(--t); }
.cve h2 a.tally:hover { text-decoration: underline; }

/* An outcome that says nothing recedes; one that says something does not.
   `withheld` is the loudest, because it is the pipeline finding evidence and
   declining to act on it — the answer to "why is this not what I expected" —
   and it is the one outcome no other CVE database publishes at all. */
.cve tr.o-no-match td, .cve tr.o-not-reached td { color: var(--faint); }
.cve tr.o-not-reached td.state { font-style: italic; }
.cve tr.o-matched td.state { color: var(--accent); }
.cve tr.o-withheld { background: var(--warnbg); }
.cve tr.o-withheld td { color: var(--warn); }
.cve tr.o-skipped td.state { color: var(--dim); }
.cve tr.decisive td { background: #eef4fb; font-weight: 600; }
.cve tr.stage th {
  background: var(--panel);
  border-top: 1px solid var(--line);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--dim);
}
/* A one-word annotation on a row — "published", "decided it". It is a label,
   so it is styled as one: small, tracked, in the link colour. The box it used
   to carry made a two-word aside compete with the row it annotates. */
.cve .tag {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .06em;
  font-weight: 700;
  color: var(--accent);
}

/* Present and absent are both answers, so neither is styled as an error. The
   row that found something is simply darker than the row that did not. */
.cve tr.no td.state, .cve tr.no th { color: var(--dim); }

/* A PoC that did not clear the evidence bar. It recedes rather than
   disappearing: the link is still worth having, and hiding it would be a
   different claim than not counting it. Same treatment an absent evidence
   source gets in .axstrip > li.no. */
table.pocs tr.uncounted td { color: var(--faint); }
table.pocs tr.uncounted a { color: var(--dim); }
p.subbar { margin: 0 0 6px; }

/* The bar is the percentile, not the score. EPSS scores are right-skewed, so
   a bar drawn from the score reads empty for nearly every CVE in the corpus
   and tells you nothing about where this one sits. */
.epssbar {
  display: inline-block;
  width: 90px;
  height: 6px;
  margin-left: 8px;
  background: var(--rule);
  border-radius: 2px;
  overflow: hidden;
  vertical-align: middle;
}
.epssbar span { display: block; height: 100%; background: var(--accent); }

.cve details.more > summary { cursor: pointer; padding: 4px 0; }
.cve details.more pre {
  margin: 4px 0 12px;
  padding: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow-x: auto;
  font-size: 12px;
  line-height: 1.45;
}

/* ---------- folded blocks ---------- */

/* Three bands fold: the exploitation evidence, the shipped rule files inside
   the posture card, and the CVSS metrics. They are one component with three
   contents, so the chrome is written once — a reader learns the affordance on
   the first one and the other two cost nothing. Only what actually differs
   (what sits on the summary line, what the hint says) is written per block. */
.exploit-block,
.cvss-block,
.timeline-block {
  margin: 16px 0 0;
  max-width: var(--w-page);
}
.exploit-details,
.cvss-details,
.timeline-details {
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg);
  overflow: hidden;
}
.exploit-details:hover,
.cvss-details:hover,
.timeline-details:hover {
  border-color: var(--control);
}
.exploit-summary,
.armr-summary,
.cvss-summary,
.timeline-summary {
  cursor: pointer;
  list-style: none;
  padding: 8px 12px;
  background: var(--panel);
  user-select: none;
}
.exploit-summary::-webkit-details-marker,
.armr-summary::-webkit-details-marker,
.cvss-summary::-webkit-details-marker,
.timeline-summary::-webkit-details-marker { display: none; }
.exploit-details[open] .exploit-summary,
.armr-details[open] .armr-summary,
.cvss-details[open] .cvss-summary,
.timeline-details[open] .timeline-summary {
  border-bottom: 1px solid var(--line);
}
.exploit-header,
.armr-header,
.cvss-header,
.timeline-header {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.exploit-header h2,
.armr-header h3,
.cvss-header h2,
.timeline-header h2 {
  margin: 0;
  padding: 0;
  border-bottom: none;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--dim);
}
.exploit-header h2::before,
.armr-header h3::before,
.cvss-header h2::before,
.timeline-header h2::before {
  content: "▸ ";
  color: var(--dim);
}
.exploit-details[open] .exploit-header h2::before,
.armr-details[open] .armr-header h3::before,
.cvss-details[open] .cvss-header h2::before,
.timeline-details[open] .timeline-header h2::before {
  content: "▾ ";
}
/* The exploit maturity is the answer this block exists to give, so it gets
   its own line, largest text in the block — bigger than the section label
   above it and the sentence below it, the way a headline sits over its
   dek. It is said as the single word in the band's own colour, no pill: the
   CVSS block still gets one because there the number and its band are a
   pair, but here the word alone is the whole claim. */
.exploit-headline {
  margin: 6px 0 0;
}
.exploit-headline .slot {
  font-weight: 700;
  font-size: 19px;
}
/* The link rides its own value, so it takes the band's colour rather than the
   link colour. It had the pill's tint behind it to say which band this was;
   with the pill gone, the word itself has to carry it. */
.exploit-headline .slot a {
  color: inherit;
}
.cvss-summary .slot {
  font-size: 12px;
}
.exploit-summary .exploit-meta {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 11px;
  color: var(--dim);
}
.exploit-summary .exploit-meta a.tally {
  color: var(--accent);
}
.exploit-summary .exploit-meta a.tally:hover {
  text-decoration: underline;
}
.exploit-summary .toggle-hint,
.cvss-header .toggle-hint {
  font-size: 11px;
  color: var(--dim);
}
.exploit-details:not([open]) .exploit-summary .toggle-hint::after {
  content: "Show evidence ▸";
}
.exploit-details[open] .exploit-summary .toggle-hint::after {
  content: "Hide evidence ▾";
}
.exploit-summary p.exploit-desc {
  margin: 4px 0 0;
  font-size: 12.5px;
  color: var(--ink);
  line-height: 1.4;
  max-width: var(--w-prose);
}
.exploit-body {
  padding: 10px 12px 12px;
}
.exploit-body .axstrip {
  margin-top: 0;
}

/* ---------- waratek stance card ---------- */

.stance-block {
  margin: 16px 0 0;
  max-width: var(--w-page);
}
.stance-card {
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg);
  padding: 10px 12px 12px;
}
.stance-header {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.stance-header h2 {
  margin: 0;
  padding: 0;
  border-bottom: none;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--dim);
}
/* Same move as the exploit block's headline: the resolution status is the
   answer this card exists to give, so it gets its own line, largest text in
   the card, above the sentence that explains it — not a pill riding the
   section label. */
.stance-headline {
  margin: 6px 0 0;
}
.stance-headline .slot {
  font-weight: 700;
  font-size: 19px;
}
.stance-headline .slot a {
  color: inherit;
}
.stance-desc {
  margin: 4px 0 8px;
  font-size: 12.5px;
  color: var(--ink);
  line-height: 1.4;
  max-width: var(--w-prose);
}
/* The delivery commitment on a RULE-IN-DEVELOPMENT record. It reads as a value
   rather than a sentence, so it takes the accent that marks a claim about the
   future, and it sits directly under the status whose first question it
   answers ("when?"). The phrase is written as a phrase in the frontmatter —
   "Expected mid-October" — so it carries no label of its own; one would only
   stutter. */
.stance-commit {
  margin: 0 0 8px;
  font-size: 13px;
  color: var(--accent);
}
/* The four axes and the decision trail (Decided by / Finding / Candidate
   fix) are the audit for the headline above, not the headline itself —
   collapsed by default like the CVSS block's metric table and the exploit
   block's evidence rows, for the same reason: detail somebody came for, not
   detail everybody reads. The ARMR rule files below stay outside this fold,
   unchanged — they're the one thing here worth showing without a click. */
.stance-details {
  margin: 0 0 8px;
}
.stance-toggle {
  cursor: pointer;
  list-style: none;
  font-size: 11px;
  color: var(--dim);
}
.stance-toggle::-webkit-details-marker { display: none; }
.stance-details:not([open]) .stance-toggle .toggle-hint::after {
  content: "Show assessment ▸";
}
.stance-details[open] .stance-toggle .toggle-hint::after {
  content: "Hide assessment ▾";
}
.stance-card .axstrip {
  margin: 6px 0 0;
}
.stance-card table.decision {
  margin: 8px 0 0;
  border-top: 1px solid var(--line);
  padding-top: 4px;
}

/* The rule files are evidence for the protection/action axes above them, so
   they live inside the card — a nested band, folded the same way the evidence
   band above the card folds. */
.armr-details {
  margin: 10px -12px -12px;
  border-top: 1px solid var(--line);
}
.armr-summary {
  cursor: pointer;
  list-style: none;
  padding: 8px 12px;
  background: var(--panel);
  user-select: none;
}
.armr-summary::-webkit-details-marker { display: none; }
.armr-details[open] .armr-summary {
  border-bottom: 1px solid var(--line);
}
.armr-header {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.armr-header h3 {
  margin: 0;
  padding: 0;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--dim);
}
.armr-header h3::before {
  content: "▸ ";
  color: var(--dim);
}
.armr-details[open] .armr-header h3::before {
  content: "▾ ";
}
/* A count beside its heading. The word is in the markup rather than the shape:
   a bare "3" needed the ring around it to read as a count at all. */
.armr-count {
  font-size: 11px;
  font-weight: 600;
  color: var(--dim);
}
.armr-header .toggle-hint {
  margin-left: auto;
  font-size: 11px;
  color: var(--dim);
}
.armr-details:not([open]) .armr-header .toggle-hint::after {
  content: "Show files ▸";
}
.armr-details[open] .armr-header .toggle-hint::after {
  content: "Hide files ▾";
}
.armr-summary p.armr-desc {
  margin: 4px 0 0;
  font-size: 12.5px;
  color: var(--ink);
  line-height: 1.4;
  max-width: var(--w-prose);
}
.armr-body {
  padding: 4px 12px 10px;
}
/* ---------- the researcher's note ---------- */

/* ---------- the researcher's note ---------- */

/* The one block on this page a person wrote, so it is the one block inside the
   posture card set as prose rather than as a band: reading measure, paragraph
   leading, real headings. It carries no fold and no toggle affordance. It is
   the last thing in the card and bleeds to its edges the way the ARMR band
   above it does, separated from the card by its own ground and a hairline —
   the page marks a change of kind with a rule, not with a coloured edge. */
.note-block {
  margin: 10px -12px -12px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--line);
  background: var(--zebra);
}
.note-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
.note-head h3 {
  margin: 0;
  padding: 0;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--dim);
}
/* The date the claim was made. It is provenance, not a value, so it sits at
   the far end of the heading line in the absence grey. */
.note-when {
  margin-left: auto;
  font-size: 11px;
  color: var(--faint);
  white-space: nowrap;
}
/* Who wrote it, and what that outranks. This is a section subtitle — the copy
   a reader skips on their second visit — so it takes .lede's grey rather than
   ink, which is reserved for the note's own sentences below. */
.note-by {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--dim);
  line-height: 1.45;
  max-width: var(--w-prose);
}

/* The rendered markdown (goldmark's output, so these are the plain tags).
   Nothing here is generated: every element came out of a sentence somebody
   typed, so it is set at the reading measure with the leading the description
   at the top of the page uses, not the 1.35 a table caption gets. */
.prose {
  max-width: var(--w-prose);
  margin-top: 6px;
}
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }
.prose p {
  margin: 0 0 8px;
  line-height: 1.5;
}
/* Every heading depth at one size. These are one writer's section breaks
   inside a block that is already a section of the page, so all a heading has
   to do here is separate; a six-level ramp inside it would compete with the
   page's own. */
.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6 {
  margin: 14px 0 2px;
  padding: 0;
  border: 0;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--dim);
}
.prose ul, .prose ol {
  margin: 0 0 8px;
  padding-left: 18px;
}
.prose li {
  margin: 0 0 3px;
  line-height: 1.5;
}
.prose li > p { margin: 0 0 4px; }
.prose blockquote {
  margin: 0 0 8px;
  padding: 4px 0 4px 10px;
  border-left: 2px solid var(--line);
  color: var(--dim);
  line-height: 1.5;
}
.prose code {
  font-size: 12px;
  background: var(--panel);
  padding: 0 3px;
  border-radius: 2px;
}
/* A fenced block — usually an ARMR template a researcher pasted in. Same
   treatment the shipped rule files get in the band above, so the example and
   the real thing look like the same kind of object, because they are. */
.prose pre {
  margin: 4px 0 10px;
  padding: 10px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 3px;
  overflow-x: auto;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.45;
}
.prose pre code {
  background: none;
  padding: 0;
  font-size: 12px;
}
.prose table {
  margin: 0 0 8px;
  border-collapse: collapse;
}
.prose th, .prose td {
  padding: 3px 8px 3px 0;
  border-bottom: 1px solid var(--line);
  text-align: left;
}

/* ---------- CVSS band ---------- */

/* Collapsed, the summary is the whole section for most records: the published
   score, its band and its vector. What opening buys is the count beside them —
   who else scored it and whether the published number is NVD's analysis or the
   CNA's own. The count is stated on the summary rather than promised by it, so
   a record with one metric does not invite a click that reveals nothing. */
.cvss-vector {
  font-size: 11.5px;
  color: var(--dim);
}
.cvss-vector code {
  font-size: 11.5px;
}
.cvss-header .toggle-hint {
  margin-left: auto;
  white-space: nowrap;
}
.cvss-details:not([open]) .cvss-header .toggle-hint::after {
  content: " ▸";
}
.cvss-details[open] .cvss-header .toggle-hint::after {
  content: " ▾";
}
.cvss-body {
  padding: 10px 12px 12px;
}
.cvss-body .lede {
  margin-top: 0;
}
.cvss-body table.metrics {
  margin-bottom: 0;
}

/* ---------- Timeline block ---------- */

.timeline-header .timeline-dates {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 12px;
}
.timeline-header .timeline-dates b {
  color: var(--ink);
  font-weight: 600;
}
.timeline-header .toggle-hint {
  margin-left: auto;
  font-size: 11px;
  color: var(--dim);
  white-space: nowrap;
}
.timeline-details:not([open]) .timeline-header .toggle-hint::after {
  content: "Show detail ▸";
}
.timeline-details[open] .timeline-header .toggle-hint::after {
  content: "Hide detail ▾";
}
.timeline-body {
  padding: 10px 12px 12px;
}
.timeline-body table.times {
  margin: 0;
}

/* ---------- affected software block ---------- */

.software-block {
  margin: 22px 0 0;
  max-width: var(--w-page);
}
.software-block .subcard {
  margin: 12px 0 0;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--bg);
  padding: 10px 14px 14px;
}
.software-block .oracle-subcard {
  border-left: 3px solid var(--accent);
}
.software-block .subcard-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.software-block .subcard-head h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}
.software-block .subcard-head .dim {
  font-size: 11px;
  color: var(--dim);
}
.software-block h4.subhead {
  margin: 14px 0 4px;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--dim);
}
/* The fixed version is the one answer in this table a reader is looking for,
   so it is green and bold. It is a version string, not a badge — the tinted
   box around it made every row of a long Maven table flicker. */
.software-block code.fix-tag {
  color: #1c7332;
  font-weight: 600;
}

/* ---------- tucked audit footer ---------- */

.cve-audit {
  margin: 24px 0 16px;
  padding-top: 10px;
  border-top: 1px solid var(--line);
  max-width: var(--w-page);
}
.audit-details {
  background: none;
}
.audit-summary {
  cursor: pointer;
  list-style: none;
  padding: 2px 0;
  font-size: 10px;
  display: flex;
  align-items: center;
  gap: 6px;
  user-select: none;
}
.audit-summary::-webkit-details-marker { display: none; }
.audit-title {
  font-weight: 400;
  color: var(--faint);
}
.audit-title::before {
  content: "▸ ";
}
.audit-details[open] .audit-title::before {
  content: "▾ ";
}
.audit-summary .dim {
  color: var(--faint);
}
.audit-body {
  padding: 8px 0 12px;
}
.audit-foot {
  margin-top: 12px;
  font-size: 11px;
  color: var(--dim);
  display: flex;
  align-items: center;
  gap: 8px;
}
.audit-foot a {
  color: var(--dim);
}
.audit-foot a:hover {
  color: var(--accent);
  text-decoration: underline;
}
.audit-foot .sep {
  color: var(--line);
}




