/* INTER, SELF-HOSTED. See static/fonts/README.md for why and for how to refresh it.

   This was a render-blocking <link> to fonts.googleapis.com plus two preconnect
   hints, on every page. The audience is CNAs and security teams, a meaningful
   share of them behind proxies that block third-party font hosts, so the site's
   typography depended on a request some readers could never complete, and every
   page made a third-party request on a site whose subject is transparency.

   ONE face, not four. Google serves the same VARIABLE font for every weight it
   declares, so the 400/500/600/700 downloads were byte-identical; `font-weight:
   100 900` lets the browser pick from the single file. The site was also asking
   for a 300 weight that nothing renders.

   `font-display: swap` is kept from Google's own declaration: text paints
   immediately in the fallback and reflows when the face arrives, which is the
   right trade for a page whose content is the point.

   The unicode-range is Google's for the latin subset, verbatim. Anything outside
   it falls through to the stack below, which is what should happen for the
   accented characters in a handful of CNA names. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("../fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* rbptracker.org additions on top of the inherited cve.icu system.
   Nothing here restyles a cve.icu component; these are the pieces that system
   does not have, because it never had to express a rule, a clock, or a
   confidence tier. */

/* ---- the lead number ---------------------------------------------------- */

/* Redaction. The one visual flourish on the site, used only for the literal
   API value, never decoratively. */

/* ---- chips ------------------------------------------------------------- */
.chip {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.16em 0.5em;
  border-radius: 3px;
  white-space: nowrap;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* CHIP TEXT COLOURS ARE FIXED TO THE WORST BACKGROUND THEY LAND ON, not to the
   one in the rule beside them.
   
   Every chip on this site failed WCAG AA in light theme, and the reason none of
   it was caught is that the chips are SEMI-TRANSPARENT: `rgba(220, 53, 69, 0.12)`
   has no fixed rendered value, only one that exists after compositing over
   whatever the row happens to be. A regex over CSS cannot composite anything, so
   the 17-test a11y suite passed while chip-none rendered at 1.75 and chip-ok, the
   marker for a satisfied launch condition, rendered at 2.41 against a bar of 4.5.
   
   The colours below are solved against `--color-bg-secondary` (#e9ecef in light),
   the darkest surface a chip sits on, so they pass everywhere else too.
   rbp/contrast.py computes the ratios and tests/test_a11y.py asserts them. */
.chip-must    { background: rgba(220, 53, 69, 0.12);  color: #b42b39; }
.chip-should  { background: rgba(108, 117, 125, 0.14); color: #5a6168; }
/* A launch condition that is satisfied. Green, but muted: this marks a promise
   kept, not a result, and nine of these turning green is the start of a launch
   decision rather than the end of one. */
.chip-ok { background: rgba(25, 135, 84, 0.14); color: #206b49; }

/* The "we cannot tell" bucket. Deliberately an outline rather than a fill, so it
   does not read as a third category of finding sitting alongside MUST and
   SHOULD. It is the absence of a measurement, and it is the largest column on
   the page, so it has to look like a gap and not like a verdict. */
.chip-unmeasured {
  background: transparent;
  color: var(--color-text-secondary);
  border: 1px dashed rgba(108, 117, 125, 0.55);
  padding: calc(0.16em - 1px) calc(0.5em - 1px);
}
.chip-late    { background: rgba(255, 193, 7, 0.18);  color: #875f00; }
.chip-corrob  { background: rgba(25, 135, 84, 0.12);  color: #146f45; }
/* .chip-block was the "named from an ID block" owner tier. v1 publishes no
   attribution, so no template emits it and the rule is dead. Removed rather
   than left: dead CSS is what let a hand-typed test list look complete while
   omitting a chip that WAS rendering. */
/* chip-none carried --color-text-muted at 1.75, which is the EXACT ratio the
   comment further down this file claims to have fixed: that fix created
   --rbp-text-muted and repointed the rules it knew about, and the chip kept the
   old token. The rule it did repoint has since been deleted; this one had not
   been found, which is the whole reason it is called out here. */
.chip-none    { background: var(--color-bg-secondary); color: #666b70; }

/* DARK THEME. Solved separately against #151821, the card surface, because a
   colour that passes over #e9ecef is not the same colour that passes over a dark
   row and three chips failed here too. chip-none needs its BACKGROUND changed as
   well: no text colour reaches 4.5 over --color-bg-secondary in dark, because
   that token is #1a1d27 and the chip is the same value as the surface. */
[data-theme="dark"] .chip-must   { color: #f87171; }
[data-theme="dark"] .chip-should { color: #9ca3b4; }
[data-theme="dark"] .chip-ok     { color: #4ade80; }
[data-theme="dark"] .chip-late   { color: #f0c25a; }
[data-theme="dark"] .chip-corrob { color: #4ade80; }
[data-theme="dark"] .chip-none   { background: #262a36; color: #b6bcc7; }

/* ---- tables ------------------------------------------------------------ */
/* A BOUNDED scrollport, so `position: sticky` on the header actually has
   something to stick to. This was `overflow-x: auto` with no max-height, which
   made the wrapper a scroll container on both axes bound to a scrollport that
   never scrolls: at 4000px of page scroll the th sat at -3,630px, so a table
   44,000px tall at live scale was read with no column labels visible at any
   point. The first columns a reader loses are Inferred owner, Confidence and
   Rule, which carry all the hedging. (Review item 17.) */
.tablewrap {
  max-height: calc(100vh - 8rem);
  overflow: auto;
}
td.num, th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: nowrap;
}
td.id { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }

/* ---- fan-out feed sub-rows (/status) ----------------------------------- */
/*
   `csaf` is one row in the feed table and seventeen providers underneath it.
   The sub-rows are subordinate, not separate: same alignment, same column for
   IDs read.

   They do NOT sum to the parent and the CSS used to tell readers to compare
   them as though they did. Providers overlap: the same advisory can be carried
   by an aggregator and by its publisher, and the parent dedupes while the rows
   do not. On the run that prompted this the sub-rows totalled 6,793 against a
   parent of 2,992. That claim is now made in the page's own copy instead of in
   a comment nobody reads.

   Indent AND a muted label, because indent alone is invisible once the table
   scrolls sideways on a narrow viewport, which is exactly where these rows are
   hardest to place. Both cues are decoration over a label that already reads
   `csaf:www.cisa.gov`, so nothing here is the only thing carrying the meaning.
*/
tr.subrow > td { border-bottom-color: var(--color-border-light); }
tr.subrow > td.mono { color: var(--rbp-text-muted); }
td.indent { padding-left: 1.6rem; }

/*
   A PROVIDER LABEL MUST BE ABLE TO BREAK, AT EVERY WIDTH.

   `csaf:csaf.data.security.nozominetworks.com` is 42 monospace characters with
   no break opportunity a browser will take on its own, and it sits in an
   indented cell. Under 768px the reflow block at the foot of this file already
   allows `.mono` to break anywhere; above it, nothing did, so the table pushed
   the PAGE sideways: measured at 192px of overflow at 769px and 61px at 900px,
   immediately above the breakpoint where the mobile rule stops applying.

   WCAG 1.4.10 is written about 320px, which is why the existing rule is where it
   is, and this failure sat in the gap that rule leaves. Scoped to this table
   rather than made global: `.mono` on the list page carries CVE IDs, which are
   short, break badly, and are meant to be selected whole.
*/
table.table-sm td.mono { overflow-wrap: anywhere; }

/* ---- caveat block ------------------------------------------------------ */
.caveat {
  border-left: 3px solid var(--color-primary);
  background: var(--color-bg-secondary);
  padding: var(--spacing-md) var(--spacing-lg);
  border-radius: 0 var(--border-radius) var(--border-radius) 0;
  margin: var(--spacing-lg) 0;
}
.caveat p:last-child { margin-bottom: 0; }
.caveat.warn { border-left-color: var(--color-warning); }

.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.small { font-size: 0.85rem; }
pre.api {
  background: var(--color-bg-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--border-radius);
  padding: var(--spacing-md);
  overflow-x: auto;
  font-size: 0.82rem;
  line-height: 1.6;
}
.stale-banner {
  background: rgba(255, 193, 7, 0.15);
  border: 1px solid var(--color-warning);
  border-radius: var(--border-radius);
  padding: var(--spacing-sm) var(--spacing-md);
  margin-bottom: var(--spacing-md);
  font-size: 0.9rem;
}

/* Quoted policy text. Marked with a rule rather than italics: these are the
   passages that cut AGAINST the site, so they have to read as the source's own
   words and not as this site's aside. */
blockquote {
  margin: 0 0 var(--spacing-md) 0;
  padding: var(--spacing-sm) var(--spacing-md);
  border-left: 3px solid var(--color-border);
  color: var(--color-text-secondary);
  font-size: 0.94rem;
}
blockquote p:last-child { margin-bottom: 0; }

/* `.degraded-banner` stood here. The banner it styled was removed from base.html
   on 2026-08-26 and the run's state is reported on /status instead, so the rule
   styled nothing. Deleted rather than left: dead CSS is what let a hand-typed
   test list look complete while omitting a chip that WAS rendering, and it is
   what let a translucent stripe fail AA in dark theme for as long as it did.

   The staleness banner is a different thing and keeps its rule below. Stale means
   "this is old", degraded meant "this is incomplete", and a run can be fresh and
   incomplete at the same time. */

/* ---- dark-mode gaps inherited from the cve.icu sheet -------------------- */
/* .metric-card fills with linear-gradient(--viz-color-1, --viz-color-2), and
   the viz palette is defined only on :root. cve.icu overrides .stat-card for
   dark but not .metric-card, so the card kept a pale blue fill on a near-black
   page. Fixed here rather than in the ported sheet, so style.css stays a clean
   copy that can be re-pulled from upstream. */
[data-theme="dark"] .metric-card {
  background: linear-gradient(135deg, var(--color-bg-content), var(--color-bg-hover));
  border-color: var(--color-border);
}
[data-theme="dark"] .metric-card:hover {
  border-color: rgba(96, 165, 250, 0.4);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}
[data-theme="dark"] .metric-value { color: var(--color-accent); }

/* ==========================================================================
   Accessibility (review item 17)
   ==========================================================================
   Two reviewers measured ten AA failures and one called them disqualifying for a
   federal reader: a documented WCAG failure on the primary data table is a bar to
   citing a third-party resource from official guidance, whatever the data quality.

   PROJECT-OWNED TEXT TOKENS, measured against every background they actually
   render on. The inherited cve.icu tokens were never re-audited against the
   striped-row background this project introduced, and auditing against white is
   the mistake to avoid: half the rows are not white.

   Measured worst-case ratios (the lowest across --color-bg-content,
   --color-bg-secondary and --color-bg-hover), pinned in tests/test_a11y.py:

     token            was (worst)   now (worst)
     text-secondary   3.95 FAIL     6.07
     text-muted       1.75 FAIL     5.44
     link             3.80 FAIL     5.43

   The 1.75 was the abstention marker, the site's own statement that it does not
   know, on the majority of rows. The least certain cell on the page was the
   least legible one, so the site's conservatism was the part a reader could not
   see. That is a semantic failure as much as a standards one. The cell it was
   measured on is gone; the tokens it produced carry the whole site. */
:root {
  /* THE INHERITED TOKENS, CORRECTED AT THE ROOT.
     
     style.css loads first and sets --color-text-secondary: #6c757d (3.95 over
     the striped row) and --color-text-muted: #adb5bd (1.75). This project
     forked AA-safe values into --rbp-* and then repointed rules at them ONE AT
     A TIME, which is why the fix kept missing things: sixteen rendered
     selectors were still on the inherited values, including .chip-unmeasured,
     td.desc, blockquote, .nav-menu a and every .footer link.
     
     Repointing rule by rule is the method that failed twice. Overriding the
     token fixes the whole class at once and cannot be missed by omission.
     Values solved against #e9ecef, the darkest surface either lands on. */
  --color-text-secondary: #5a6168;
  --color-text-muted: #666b70;
  /* Hover states are text a reader reads while pointing at it, so they carry
     the same 4.5 bar. #0d6efd measured 3.80 on the striped row. */
  --color-primary: #0b5bd2;
  /* .logo:hover, .nav-menu a:hover and .footer a:hover all read this. */
  --color-accent: #0b5bd2;
  /* .metric-value, the big figure on every stat card, read this at 2.63. */
  --chart-primary: #10538f;

  --rbp-text-secondary: #52585e;
  --rbp-text-muted: #595f66;
  --rbp-link: #0a58ca;
  /* The days-public signal. NOT --color-warning: that is #ffc107 and
     renders at 1.37:1 on white, which the contrast harness caught the
     moment the list page used it. Semantic colour still has to be
     readable; a signal nobody can read is decoration. */
  --rbp-age: #8A5013;
}
[data-theme="dark"] {
  /* Same corrections for dark. --color-text-muted is #6b7280 there, which is
     3.67 on the card surface. */
  --color-text-muted: #9aa3b2;
  /* --color-text-secondary HAS TO BE RE-ASSERTED HERE, and it was missing.
     
     The :root block above overrides it to #5a6168, an AA-safe value solved
     against the LIGHT surfaces. style.css sets the dark value to #9ca3b4 in its
     own [data-theme="dark"] block. Those two selectors have EQUAL specificity
     (0,1,0), so source order decides, and rbp.css loads second: the light
     correction won in dark theme and --color-text-secondary resolved to #5a6168
     against a near-black ground.
     
     Measured in the browser at 2.54:1 against a bar of 4.5, on `.text-muted`,
     which style.css sets from this token with `!important` in dark. That is the
     footer of every page, the card sub-copy on /method, /policy and /status, and
     the "how far the feeds reach" prose.
     
     The comment on the :root block argues that overriding the TOKEN "fixes the
     whole class at once and cannot be missed by omission", against repointing
     rules one at a time. That argument is right and this is its one failure
     mode: overriding a token at :root silently un-overrides the other theme
     unless you re-assert it, and nothing about the light theme looks wrong.
     
     #9ca3b4 is style.css's own dark value, which the harness had believed was
     winning all along. 6.32 worst-case across the four dark surfaces. */
  --color-text-secondary: #9ca3b4;
  /* .theme-toggle:hover reads --color-primary over --color-bg-hover. */
  --color-primary: #6ba5f8;
  --color-accent: #6ba5f8;
  --chart-primary: #6ba5f8;
  --rbp-text-secondary: #b6bece;
  --rbp-text-muted: #9aa3b2;
  --rbp-link: #7cb0ff;
  --rbp-age: #D9A05B;
}

/* THIS RULE CARRIED THREE DEAD SELECTORS AND TWO LIVE ONES, across two lines
   with only the second holding the brace, so "delete the dead line" took the
   live half with it twice. The dead three are gone and the body stays.

   WHAT IT IS ACTUALLY WORTH, measured rather than inherited, because the note
   that used to sit here said losing it dropped `.text-muted` to 2.54:1 in dark
   and that is not true. In DARK this rule never applies at all: style.css sets
   `[data-theme="dark"] .text-muted` from --color-text-secondary with
   `!important`, which wins outright. The 2.54 was that token going un-asserted
   for dark, which is a different fix, still in place two blocks up, and guarded
   by test_a_root_override_does_not_silently_undo_the_dark_theme.

   What this rule buys is LIGHT theme, where it wins on source order and swaps
   #5a6168 for the darker #52585e: 6.07 worst case against 5.30 without it.
   Both clear AA, so deleting it is a real loss of headroom and not a failure,
   and no test goes red if it goes. Keep it, and keep the reason honest: the
   next person to read this needs to know which half of the pair is load-bearing
   and that the scary number belongs to the other one. */
.page-header p.text-muted, .text-muted { color: var(--rbp-text-secondary); }

/* FOCUS. One outline rule existed in the entire project, so no focus treatment
   was ever designed, on a site whose primary surface is a sortable table. */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
input:focus-visible,
select:focus-visible {
  outline: 3px solid var(--rbp-link);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Empty is not a warning, so it does not get .caveat warn's amber. */
.empty-state {
  border: 1px dashed var(--color-border);
  border-radius: var(--border-radius);
  padding: var(--spacing-lg);
  color: var(--rbp-text-secondary);
  text-align: center;
  margin: var(--spacing-lg) 0;
}
.empty-state strong { color: var(--color-text-primary); }

/* 768, NOT 767, and the single pixel was a real failure. It applies to the
   reflow block further down, which is now the only 768 block in this file.
   
   style.css opens `@media (max-width: 768px)` with `table { min-width: 600px }`
   and `th, td { white-space: nowrap }`. A block opening at 767px leaves EXACTLY
   768px, the iPad portrait width, with both of those on and nothing undoing
   them. Whatever else changes here, do not move the number by one. */
@media (max-width: 768px) {
  .tablewrap { max-height: none; overflow: visible; }
}

/* PRINT stripped every hedge. The inherited print block forces
   `color: #212529 !important` on td, th, span and a, which collapses the entire
   certainty vocabulary to one ink: a candidate MUST becomes indistinguishable
   from a SHOULD, and the abstention marker loses its distinction. It also never
   reset .tablewrap's overflow or the table's min-width, so an overflow box with
   no scrollbar clipped the page. */
@media print {
  .tablewrap { max-height: none !important; overflow: visible !important; }
  /* AND THE TABLE'S OWN BOX, which is a separate container from the wrapper.
     The reflow block above gives `table.table-sm` `display: block; overflow-x:
     auto` so a wide figure table scrolls inside itself instead of scrolling the
     page. That block carries no media type, so it matches PAPER too, where a
     scroll container is a clip with no scrollbar and the reader loses the
     columns silently. It also stops being a table, so the columns stop lining
     up. The equivalent reset for the deleted `table.rbp` was here and left with
     it; this is the same guarantee for the tables that are actually printed. */
  table.table-sm { display: table !important; overflow-x: visible !important; }
  /* Keep the vocabulary legible in one ink by giving each chip a border and a
     visible label, rather than relying on colour that print flattens. */
  .chip { border: 1px solid #212529 !important; background: transparent !important; }
  .chip-must::after { content: " (MUST)"; }
  .chip-unmeasured::after { content: " (not measurable)"; }
  .caveat, .empty-state, blockquote { border: 1px solid #212529 !important; }
  /* A printed page cannot be scrolled to the caveats, so keep them with the table. */
  .caveat { page-break-inside: avoid; }
  .nav, .theme-toggle, .nav-toggle, .skip-link { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* The front page's h1. The lead count is the visual heading, so the h1 names the
   thing and sits above it small: the page that will be ranked and linked most had
   no h1 at all and an outline starting at H2. Not visually-hidden, because a
   heading a sighted reader cannot see is its own problem. */


/* ---- narrow viewports: nothing may scroll the PAGE sideways ------------- */
/*
   WCAG 1.4.10 (reflow) is failed by horizontal page scroll at 320 CSS px. This
   site had 926px of it on /cves and 1,656px on /method at 375px wide.
   
   The .rbp tables get the card layout above. These are the OTHER tables, the
   coverage figures, the feed inventory and the launch checklist, which are
   `table.table-sm` and were inheriting `min-width: 600px` and `nowrap` from
   style.css with nothing to undo them. They stay tabular, because a
   three-column figure table reads worse as stacked cards, and instead scroll
   inside their own bounded container so the PAGE does not.
*/
@media (max-width: 768px) {
  table.table-sm {
    min-width: 0;
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  table.table-sm th,
  table.table-sm td {
    white-space: normal;
    overflow-wrap: anywhere;
  }
  /* Long unbroken strings are the other source of sideways scroll: CVE IDs,
     URLs and mono spans in prose. */
  .mono, code, pre {
    overflow-wrap: anywhere;
    word-break: break-word;
  }
  pre.api {
    overflow-x: auto;
    white-space: pre-wrap;
  }
}


/* White text on a light-blue fill is 2.51 in dark theme. These are the only
   rules that put `white` on --color-primary, and in dark that token is a pale
   blue chosen for TEXT on a dark ground, not for a fill behind white. */
[data-theme="dark"] .skip-link,
[data-theme="dark"] .page-header button:active {
  color: #0b1220;
}


/* ---- the nav band between the mobile collapse and comfortable ----------- */
/*
   Measured at 72 to 79px of horizontal PAGE overflow across 769 to 847px on
   seven of nine pages, with the theme toggle pushed entirely off screen. The
   mobile menu collapses at 768px and the full nav does not fit until the high
   840s, so there is an 80px band where neither layout works.
   
   `flex-wrap: wrap` is the mechanism fix and is width-independent: the exact
   pixel at which the nav stops fitting depends on font metrics, so a breakpoint
   number alone would be right on the machine it was measured on and wrong on
   the next one. The wider collapse is belt and braces.
*/
.nav { flex-wrap: wrap; }
.nav-menu { flex-wrap: wrap; }

@media (max-width: 900px) {
  /* Collapse the menu across the whole band rather than at 768 only. */
  .nav-menu { gap: var(--spacing-sm); }
  .nav-menu a { padding-left: 0.4rem; padding-right: 0.4rem; }
}

/* ---- "Showing up in": the evidence column ------------------------------- */
/*
   This is the column the site exists for. A reserved ID referenced in three
   independent public advisories IS the finding, and it used to render as the
   string "osv,ghsa,debian" in a mono span next to a separate integer count.
   Now every source is the link to the page it is showing up on, and the count
   is the number of chips a reader can see without counting anything.
*/
.where { line-height: 2; }
/* THE MEASURE. Body prose ran at 140-146 characters per line against a 45-75
   readable band, on every page, because only the lead had a max-width. One
   rule, applied where prose actually is. */
.page-header p, .card-body > p, .caveat p { max-width: 68ch; }

/* ==========================================================================
   THE LIST PAGE. The lead, the source inventory, a sticky control bar, and the
   rows under their group headings.

   Built on the EXISTING tokens rather than a new palette, so the themes and the
   computed contrast tests in tests/test_a11y.py keep applying unchanged. That is
   a real constraint on the look and it is deliberate: tests/test_a11y.py
   DISCOVERS the selectors it measures out of this file, so a hand-picked colour
   introduced here is measured on the next run whether or not anyone remembered
   to add it to a list. Anything that wants a new colour has to be solved against
   every surface it lands on first.
   ========================================================================== */

.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
body.locked{overflow:hidden}

/* ---- the lead: the count at full scale, the distribution beside it ---- */
/* The count was 1.7rem inside the sticky bar, between a search box and two
   selects. It is the whole product of the site and it was set smaller than the
   nav. Two columns rather than one so the shape of the data sits at the same
   altitude as the number it describes: they are one claim and its evidence, and
   stacking them puts a screen between them. */
.lead{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);
  gap:14px 56px;align-items:start;padding:26px 0 4px}
.lead-eyebrow{font-size:.7rem;letter-spacing:.19em;text-transform:uppercase;
  color:var(--rbp-text-muted);margin:0 0 10px}
/* h1.cmd-count, not div: the count IS the front page's heading. It keeps the
   class name because tests/test_copy.py reads `<h1 class="cmd-count">` to check
   that the headline and og:description carry the same count expression. */
.cmd-count{display:block;margin:0;font-weight:400;font-size:1rem}
.cmd-count b{display:block;font-size:clamp(3.4rem,9vw,6.4rem);font-weight:650;
  letter-spacing:-.045em;line-height:.88;font-variant-numeric:tabular-nums;
  color:var(--color-text-primary)}
.cmd-count b.dim{opacity:.45}
/* ONE LINE. The unit is the subject of the heading, and "reserved, public,
   unpublished" broken after the second comma reads as a list that ran out of
   room rather than as the phrase it is. It was wrapping because of a 22ch
   measure inherited from when this sat inside the command bar and had a search
   box beside it competing for the width; nothing is beside it now. */
.cmd-count span{display:block;margin-top:12px;font-size:1.12rem;line-height:1.35;
  white-space:nowrap;color:var(--color-text-primary)}
.lead-floor{margin:16px 0 0;font-size:.85rem;line-height:1.55;max-width:44ch;
  color:var(--rbp-text-secondary)}
.lead-floor b{color:var(--color-text-primary)}
/* The scope of the count, directly under it. Same secondary colour as the floor
   note so it reads as qualification rather than as a second claim, and sized
   between the count and the floor text: a reader who takes in only the number
   and the line under it has still been told the window. */
.lead-window{margin:10px 0 0;font-size:.92rem;line-height:1.4;font-weight:500;
  color:var(--rbp-text-secondary)}

/* ---- the distribution: the legend for the list ---- */
.dist{margin:0;border-top:2px solid var(--color-text-primary);padding-top:14px}
.dist-cap{display:flex;flex-wrap:wrap;justify-content:space-between;
  align-items:center;gap:8px 16px;margin-bottom:14px}
.dist-lab{display:flex;align-items:center;gap:8px;font-size:.82rem;
  color:var(--rbp-text-secondary)}
.dist-lab select{font:inherit;font-size:.86rem;font-weight:600;
  color:var(--color-text-primary);background:var(--color-bg-content);
  border:1px solid var(--color-border);border-radius:6px;padding:5px 9px;cursor:pointer;
  /* A <select> sizes itself to its widest option, and these are page-layout
     decisions taken by the longest label. Bounded so the caption cannot push the
     lead sideways at 320px. WCAG 1.4.10. */
  max-width:min(20rem,62vw)}
.dist-sub{font-size:.72rem;color:var(--rbp-text-muted);
  font-variant-numeric:tabular-nums}
.distbars{display:flex;flex-direction:column;gap:7px}
/* One series, one hue: length carries the magnitude and every bar is labelled
   with its own count, so there is nothing a legend would disambiguate. Each bar
   is also the control that filters to it, which is the affordance the chart
   earns once it is the legend for the list. */
.distbar{display:grid;grid-template-columns:minmax(0,9.5rem) 1fr 3.4rem;
  gap:12px;align-items:center;width:100%;
  background:none;border:0;border-radius:5px;padding:3px 5px;margin:0 -5px;
  font:inherit;text-align:left;cursor:pointer;min-height:0}
/* WRAPS, does not ellipsise. The excluded buckets carry "not in view" after the
   name, and at 9.5rem that came back as "6 to 12 months NOT IN ..." on the one
   row whose whole job is to say what is being held back. A marker that is itself
   truncated is worse than no marker. */
.distbar > span{font-size:.78rem;color:var(--rbp-text-secondary);line-height:1.25;
  overflow-wrap:anywhere}
.distbar > i{height:14px;border-radius:3px;background:var(--color-bg-secondary);display:block}
.distbar > i > b{display:block;height:100%;border-radius:3px;
  background:var(--rbp-age);min-width:3px}
.distbar > u{text-decoration:none;font-size:.78rem;text-align:right;
  color:var(--color-text-primary);font-variant-numeric:tabular-nums}
.distbar:hover{background:var(--color-bg-secondary)}
.distbar[aria-pressed="true"] > span{color:var(--color-text-primary);font-weight:600}
/* THE BUCKETS THE AGE BOUND IS HOLDING BACK, drawn at their true height and
   marked. The default view hides 400 rows including the oldest, which the notice
   above the list states in words; this is the same fact as a shape, so a reader
   sees how much is being held back rather than reading that some is. Marked with
   a label as well as opacity: a state carried only by opacity is not a state. */
.distbar.out > i > b{opacity:.4}
.distbar.out > span,.distbar.out > u{color:var(--rbp-text-muted)}
.distbar.out em{display:block;font-style:normal;font-size:.66rem;letter-spacing:.06em;
  text-transform:uppercase;color:var(--rbp-text-muted)}

/* ---- the source inventory ---- */
/* EVERY CONFIGURED FEED, not every feed with rows. This was a <select> built
   from the slugs in today's rows: the two quiet feeds of thirteen had no entry
   at all, and nothing on the page said how many feeds there are, so a reader
   could not tell whether what they were looking at was all of them. */
/* BELOW THE ROWS NOW, so the space it takes is space after the data rather
   than before it. It was 582px of a 812px phone screen between the count and
   the first CVE ID. */
.seenin{margin:34px 0 0;padding:18px 0 0;border-top:1px solid var(--color-border)}
.seenin-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 14px;
  margin-bottom:11px}
.seenin h2{font-size:.72rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--rbp-text-muted);margin:0;font-weight:600}
.seenin-note{margin:0;font-size:.76rem;color:var(--rbp-text-muted)}
/* A GRID THAT GETS DENSER RATHER THAN TALLER.

   The chip row was the opposite: every source added to the site added width
   until it wrapped, and then added a line. Seventeen feeds plus seven CSAF
   publishers ran 198px on a desktop and 582px on a 375x812 phone, and it sat
   between the count and the rows.

   `auto-fill` rather than a column count, so the browser decides how many
   columns fit and this file does not have to be kept in step with the feed
   list. Three columns at 1280, two on a tablet, one on a phone, where the
   section is now below the rows and its height costs nothing.

   330px is a measured floor rather than a round number. At 228 the grid fitted
   four columns at 1280 and the name track came out at 118px, which truncated
   "Red Hat Product Security", "Nationaal Cyber Security Centrum" and "Schneider
   Electric CPCERT" all at once -- the three entries whose whole reason for being
   nested is that they are distinguishable from the feed one row up. A column
   count that cuts the names is not denser, it is just smaller.

   `min(330px,100%)` INSIDE THE MINMAX, AND THAT IS NOT DECORATION. A bare
   `minmax(330px,1fr)` track cannot go under its minimum, so at 320px the grid
   laid a 330px column in a 320px viewport and scrolled the document sideways --
   caught by the 320px case of
   test_a_source_label_at_its_cap_does_not_scroll_the_page_sideways, which is the
   test that exists because a source label is the one thing on this page whose
   width comes from outside the page. Wrapping the minimum in `min(_,100%)` lets
   the single column collapse to whatever there is. */
.srcgrid{margin-top:2px}
.srcrow{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(330px,100%),1fr));
  gap:0 34px}
.srcrow.sub{display:block;margin-top:18px;padding-left:14px;
  border-left:2px solid var(--color-border)}
.srcsub{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(330px,100%),1fr));
  gap:0 34px}
.srclab{display:block;font-size:.66rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--rbp-text-muted);margin:0 0 5px}

/* THE NAME SHRINKS, THE COUNT DOES NOT.

   `overflow:hidden` on the button clips a long child but cannot ellipsise one:
   text-overflow applies to a box's own inline content, and the name is a span.
   So at 320px the 50-character publisher name laid out 315px wide inside a
   control capped narrower than that, and the button simply cut whatever came
   last, which is the COUNT. "Bundesamt fur Sicherheit in der Informationstechnik"
   rendered with its number missing, and a facet with no count is the one part of
   this control a reader cannot reconstruct from anywhere else on the page.

   In a grid the fix is the same as it was in the flex row: `minmax(0,1fr)` on
   the name track is what actually lets it shrink, so the part that gets cut is
   the part that is still legible when cut. The bar and the count have fixed
   tracks and are never the thing that gives. */
.srcitem{display:grid;grid-template-columns:minmax(0,1fr) 96px 3.6rem;
  align-items:center;gap:0 12px;width:100%;text-align:left;
  font:inherit;font-size:.8rem;background:none;color:var(--rbp-text-secondary);
  border:0;border-bottom:1px solid var(--color-border);border-radius:0;
  padding:7px 2px;cursor:pointer;min-height:0}
.srcitem .srcname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* THE SAME HUE AS THE AGE DISTRIBUTION, and `--rbp-age` is the site's one data
   colour rather than an age-specific one. One series, one hue, in both places
   that draw a magnitude: length carries the size and the number is on the line,
   so there is nothing a second colour would disambiguate.

   Decorative, and marked so. The count beside it is the accessible reading of
   the same fact, and a screen reader being read a bar as well is being read the
   number twice. */
.srcbar{height:6px;border-radius:3px;background:var(--color-bg-secondary);
  display:block;overflow:hidden}
/* A FACET WITH ROWS ALWAYS DRAWS A MARK. CERT/CC at 12 against a 1,312 top bar
   is 1%, and 1% of a 96px track is one pixel, which renders as nothing: the
   line would read as a feed that contributed zero next to a dashed entry that
   actually did. Same floor and same reason as `.distbar > i > b`. */
.srcbar i{display:block;height:100%;border-radius:3px;background:var(--rbp-age);
  min-width:3px}
.srcitem u{text-decoration:none;color:var(--rbp-text-muted);text-align:right;
  font-variant-numeric:tabular-nums}
.srcitem:hover{color:var(--color-text-primary);
  border-bottom-color:var(--rbp-link)}
/* THE SELECTED STATE IS A MARK AND A WEIGHT, NOT A FILL.

   It was a blue fill with the label reversed out of it, which reads well and
   cannot be verified: tests/test_a11y.py composites a rule's OWN background, so
   the control itself measured fine while the count nested inside it declared a
   colour with no background of its own and was measured against the page
   surfaces instead of the fill it actually lands on. White on white, passing.

   Keeping every colour on a surface the harness already knows about means the
   ratios stay solved rather than argued about. The check mark carries the state
   as well as the colour does, so the selection is not colour alone either. */
.srcitem[aria-pressed="true"]{color:var(--rbp-link);font-weight:600;
  border-bottom-color:var(--rbp-link)}
.srcitem[aria-pressed="true"] .srcname::before{content:"\2713\00a0"}
.srcitem[aria-pressed="true"] u{color:var(--rbp-link)}
/* A FEED THAT RETURNED NOTHING IS STILL A FEED THAT WAS READ, so it is listed at
   zero rather than dropped. Recessive, because it is not a route to any rows,
   but still a control: choosing it gives an honest empty view, which is what the
   old select could not do -- it had no option for a quiet feed, so `?src=mozilla`
   fell through to every row. An empty dashed track rather than a filled one,
   because the shape has to say "nothing" without saying "very little". */
.srcitem.zero{color:var(--rbp-text-muted)}
.srcitem.zero .srcbar{background:none;border:1px dashed var(--color-border)}
/* The denominator the bars are read against, so it is the line above them
   rather than one of them. */
.srcitem.all{border-bottom-color:var(--rbp-text-muted)}
.srcitem.all .srcname{font-weight:600;color:var(--color-text-primary)}
.srcitem.all .srcbar{visibility:hidden}
.srcitem.all u{color:var(--color-text-primary)}

/* ---- the source filter, in the bar with the other filters ---- */
/* CONSTANT HEIGHT AT ANY NUMBER OF SOURCES, which is the property the chip row
   could not be given. The list inside grows; the page does not. */
.srcpick{position:relative;display:flex;align-items:center;gap:6px}
/* THE SAME TYPE AS "PUBLIC" ONE CONTROL OVER, and declared next to it rather
   than near the popover, because the thing being kept in step is the pair. */
.srckey{font-size:.78rem;color:var(--rbp-text-muted);flex:0 0 auto}
.srcbtn{display:inline-flex;align-items:center;gap:7px;font:inherit;
  font-size:.82rem;height:38px;padding:0 11px;border:1px solid var(--color-border);
  border-radius:6px;background:var(--color-bg-secondary);
  color:var(--color-text-primary);cursor:pointer;min-height:0;
  max-width:min(20rem,60vw)}
.srcbtn-lab{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* THE CARET IS THE ONLY THING SAYING THIS OPENS SOMETHING. The age control
   beside it is a native select and draws its own; this is a button and would
   otherwise read as an action. */
.srcbtn::after{content:"";flex:0 0 auto;width:6px;height:6px;
  margin:-3px 0 0 1px;border-right:2px solid var(--rbp-text-muted);
  border-bottom:2px solid var(--rbp-text-muted);transform:rotate(45deg)}
.srcbtn:hover{border-color:var(--rbp-link)}
/* A FILTER THE READER CANNOT SEE IS THE SAME DEFECT AS ONE THAT SILENTLY STOPPED
   APPLYING, from the other direction. The trigger carries the selection in its
   own text, and the mark means the state is not colour alone. */
.srcbtn.on{border-color:var(--rbp-link);color:var(--rbp-link);font-weight:600}
.srcbtn.on .srcbtn-lab::before{content:"\2713\00a0"}
.srcmenu{position:absolute;top:calc(100% + 6px);right:0;z-index:30;width:min(21rem,86vw);
  background:var(--color-bg-content);border:1px solid var(--color-border);
  border-radius:8px;padding:8px;box-shadow:0 8px 24px rgba(0,0,0,.16)}
.srcmenu-find input{width:100%;padding:7px 10px;font:inherit;font-size:.82rem;
  border:1px solid var(--color-border);border-radius:6px;
  background:var(--color-bg-secondary);color:var(--color-text-primary)}
/* THE ONE PLACE ON THIS PAGE THAT SCROLLS INSIDE ITSELF, and it is the reason
   the control is a fixed height: the list is bounded here rather than in the
   page. Twelve entries fit before it scrolls, which is more than the site reads
   today and less than it will. */
.srcmenu-list{max-height:min(50vh,22rem);overflow-y:auto;margin-top:6px}
.srcopt{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:0 12px;
  align-items:baseline;width:100%;text-align:left;font:inherit;font-size:.82rem;
  background:none;color:var(--rbp-text-secondary);border:0;border-radius:5px;
  padding:6px 8px;cursor:pointer;min-height:0}
.srcopt .srcname{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.srcopt u{text-decoration:none;color:var(--rbp-text-muted);
  font-variant-numeric:tabular-nums}
.srcopt:hover{background:var(--color-bg-secondary);color:var(--color-text-primary)}
.srcopt[aria-pressed="true"]{color:var(--rbp-link);font-weight:600}
.srcopt[aria-pressed="true"] .srcname::before{content:"\2713\00a0"}
.srcopt[aria-pressed="true"] u{color:var(--rbp-link)}
.srcopt.zero{color:var(--rbp-text-muted)}
.srcopt.all{border-bottom:1px solid var(--color-border);border-radius:5px 5px 0 0;
  margin-bottom:4px}
/* NESTED HERE TOO, for the reason it is nested in the ledger: `role="group"`
   with a name is what tells a screen reader that "Red Hat Product Security" is
   inside CSAF Advisory rather than beside Red Hat. */
.srcopt-grp{margin-top:6px;padding-left:10px;border-left:2px solid var(--color-border)}
.srcmenu-none{margin:8px;font-size:.8rem;color:var(--rbp-text-muted)}

/* ---- the control bar, sticky, directly above the rows ---- */
.cmdbar{position:sticky;top:0;z-index:20;background:var(--color-bg-content);
  border-bottom:1px solid var(--color-border);margin-top:22px}
/* THE WIDEST CONTROL ON THE PAGE HAD THE TIGHTEST INSIDE.

   12px of inner padding is right for a chip. On a field 990px wide it puts the
   placeholder, and then every character typed into it, hard against the frame at
   the left end, and any text long enough to reach the right end does the same
   there. This is the one control on the page a reader looks at while typing.

   The gap to the age control widens with it, because the field's right end was
   14px from the word "Public" and at this width that reads as the two controls
   touching. */
/* THE CONTROLS SIT INSIDE THE BAR RATHER THAN ON ITS EDGES.

   `.cmdbar` paints an opaque background -- it has to, it is sticky and the rows
   scroll under it -- and that background was exactly the content width, so the
   search field sat on its left edge and the age select on its right with nothing
   between either control and the end of the box it lives in.

   THIS IS A TRILEMMA AND THE THIRD ONE GIVES. The bar can be the content width,
   the controls can be inset from it, and the controls can line up with the hard
   left edge every other thing on this page starts on -- the eyebrow, the count,
   the floor sentence, the view note, every group heading, every CVE ID. Any two.
   Bleeding the bar outward was the version that kept the alignment, and it reads
   as the bar overhanging the page rather than as the controls having room, which
   is the wrong two. So the field starts 16px in and is the one thing on the page
   off that line, which is legible as a toolbar with its own padding in a way an
   overhanging bar is not. */
.cmd-in{display:flex;align-items:center;gap:24px;flex-wrap:wrap;padding:10px 16px}
/* THE SEARCH TAKES THE ROW BACK, and the crowding is fixed by the gap instead.

   It was capped at 640 with `margin-inline-end:auto` holding the filters at the
   right end, on the theory that a search on one side and filters on the other
   would read as deliberate. It does not, and the reason is that `.container` is
   a hard 1200px: the freed space cannot go anywhere useful, so it pooled as a
   fixed 238px hole in the middle of the bar at every desktop width, which is the
   largest empty space on the page and reads as a missing control rather than as
   composition. Two other placements were built and measured before this landed
   -- packing left leaves 214px falling off the right end of a row whose content
   below runs full width, and parking the live count in the gap still left 128px
   and an orphaned label in it.

   What actually read as crowded was never the width. It was three controls at
   three heights with two different label patterns and 20px between all of them.
   Those are fixed above and beside; the field can have the row.

   THE GAP CARRIES THE GROUPING. 24px inside the filter pair, 42 between the
   search and it, so the bar reads as one control and then two rather than three
   in a line. Expressed as a margin on the first filter rather than as a second
   `gap`, because a flex gap cannot differ between one pair of items and the
   next. */
.cmd-grow{flex:1 1 240px;min-width:180px}
.srcpick{margin-inline-start:18px}
/* ONE ROW, ONE HEIGHT. The three controls in this bar were 43.6px, 37px and
   33.5px, each arriving at its own height from its own padding against its own
   font size and its own default line-height -- 16px inherited in the field, .82
   in the two filters, `normal` in the select where the other two compute a
   ratio. Ten pixels of difference across three boxes sitting on one line is most
   of what reads as clutter, ahead of any of the widths.

   Set on the box rather than reached at through padding, because padding cannot
   reconcile three different line-height regimes and this is a toolbar: the
   height is a property of the row, not of what each control happens to contain.
   38px clears the 24px WCAG 2.5.8 AA target on a phone and is the height the age
   select, at 33.5, was already quietly setting for the group.

   The field keeps its 16px type. Anything smaller makes iOS zoom the page on
   focus, which is a worse defect than a tall input. */
.cmd-grow input{width:100%;height:38px;padding:0 16px;border:1px solid var(--color-border);
  border-radius:6px;background:var(--color-bg-secondary);color:var(--color-text-primary);font:inherit}
.cmd-lab{font-size:.78rem;color:var(--rbp-text-muted);display:flex;align-items:center;gap:6px}
.cmd-lab select{height:38px;padding:0 9px;border:1px solid var(--color-border);border-radius:6px;
  background:var(--color-bg-secondary);color:var(--color-text-primary);font:inherit;font-size:.82rem;
  max-width:min(22rem, 60vw);text-overflow:ellipsis}
.linkbtn{background:none;border:none;color:var(--rbp-link);cursor:pointer;
  padding:6px 2px;font:inherit;text-decoration:underline;text-underline-offset:3px}

/* ---- rows ---- */
/* The default-view notice. Quiet, but not muted to the point of being skipped:
   it carries the total the reader arrived expecting and the control that gets
   them the rest of the rows. */
.viewnote{margin:14px 0 -4px;font-size:.82rem;color:var(--rbp-text-secondary);
  line-height:1.45}
.viewnote b{color:var(--color-text-primary);font-variant-numeric:tabular-nums}
/* AN INLINE CONTROL MUST NOT INFLATE THE LINE IT SITS IN.

   style.css gives every `button` `min-height:44px` below 768px, which is right
   for the controls it was written for: they are all flex or block items. A button
   sitting INLINE at the end of a sentence is not, and a 44px inline-block in a
   19.7px line box makes that line box 44px, so the sentence renders with a
   visibly wider gap above its last line than between any of the others.

   The touch target is restored as a ::before overlay that grows OUTSIDE the line
   box: 24px clears WCAG 2.2 AA (2.5.8) where the 44px AAA target is what an
   inline context cannot have. */
.viewnote .linkbtn,.lead-floor .linkbtn{min-height:0;min-width:0;padding:0 2px;
  line-height:inherit;position:relative}
.viewnote .linkbtn::before,.lead-floor .linkbtn::before{content:"";position:absolute;inset:-3px -7px}

/* THE GROUP HEADING. Emitted wherever the group key changes in the sorted slice,
   so it is a property of the order rather than a second structure that can
   disagree with it.

   A REAL <h2>, not a styled div. The page outline is otherwise a flat run of
   2,037 rows with no landmarks in it, and the headings are the one thing that
   makes the list navigable by a screen reader's heading list. The count is the
   group's size in the FILTERED set rather than the number drawn so far, because
   the list is windowed and a heading reporting 100 for a group of 1,358 is worse
   than no heading. */
.grouphead{display:flex;align-items:center;gap:14px;margin:26px 0 8px;
  font-size:.92rem;font-weight:600;letter-spacing:-.005em;
  color:var(--color-text-primary)}
.grouphead:first-child{margin-top:14px}
.grouphead span{white-space:nowrap}
.grouphead i{flex:1;height:1px;background:var(--color-border)}
.grouphead u{text-decoration:none;font-size:.74rem;font-weight:400;
  color:var(--rbp-text-muted);font-variant-numeric:tabular-nums;white-space:nowrap}

.rbplist{display:flex;flex-direction:column;gap:0;margin:0 0 10px}
/* NO CARD, NO RAIL. Every row was an identical bordered box, so a 970-day row
   and a 7-day row looked the same; the one cue that varied was a 12px strip at
   the card edge, 8px on a phone. Rows are separated by a hairline and the wait
   is the largest thing in them, so the list reads as a record rather than as a
   feed of cards. */
.rbprow{border-top:1px solid var(--color-border)}
.rbprow:last-child{border-bottom:1px solid var(--color-border)}
.rbprow > summary{list-style:none;cursor:pointer;display:grid;
  grid-template-columns:1fr auto 24px;align-items:center;gap:0 22px;
  padding:13px 4px}
.rbprow > summary::-webkit-details-marker{display:none}
.rbprow > summary:hover{background:var(--color-bg-hover)}
/* THE DISCLOSURE AFFORDANCE, and there was none at all before it was drawn here.

   Removing the native marker and replacing it with nothing left a row that
   looked static, with `cursor:pointer` as the only hint, which needs a mouse
   already on the row to be seen. Everything behind the interaction is the
   evidence -- the per-feed first-seen dates and the "open advisory" links -- so
   on the primary page of a site built to be cited, the citations sat behind a
   control a reader had no way to know existed.

   It is decorative: <details> already exposes its own open/closed state to
   assistive technology, so an aria-label here would be a second announcement. */
.rbprow > summary::after{
  content:"";align-self:center;justify-self:center;
  width:7px;height:7px;margin-top:-3px;
  border-right:2px solid var(--rbp-text-muted);
  border-bottom:2px solid var(--rbp-text-muted);
  transform:rotate(45deg);
  transition:transform .15s ease}
.rbprow[open] > summary::after{transform:rotate(-135deg);margin-top:3px}
.rbprow > summary:hover::after,
.rbprow > summary:focus-visible::after{border-color:var(--rbp-link)}
/* A rotation is the one thing on this page that moves, so it is also the one
   thing that has to respect the setting. */
@media (prefers-reduced-motion:reduce){
  .rbprow > summary::after{transition:none}
}
.rowbody{min-width:0}
.idline{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.cve{font-weight:600;font-size:.96rem;letter-spacing:-.01em;overflow-wrap:anywhere}
/* Package coordinates are the same shape as the descriptions below and worse:
   `@ooples/token-optimizer-mcp` is one token, and an npm scope has no spaces in
   it at all. */
.pkg{font-size:.76rem;color:var(--rbp-text-muted);overflow-wrap:anywhere}
/* BREAKS WHEN IT HAS TO, and it had nothing telling it to.

   The most common description on this site is a `ghsa-repos` one, and its shape
   is "AcademySoftwareFoundation/OpenImageIO repository advisory
   GHSA-xxxx-xxxx-xxxx": two tokens with no break opportunity a browser will take
   on its own, the longer of them 37 characters. That is 1,173 of 2,037 rows, not
   an edge case. At 320px it set the row's minimum content width to 300px inside
   a 236px column and pushed the PAGE sideways. WCAG 1.4.10.

   It never showed while the rows were cards, because `.rbprow` was
   `overflow:hidden` and clipped the description instead -- the same masking that
   hid the publisher chip, found the same way. `anywhere` rather than
   `break-word`, so ordinary prose still breaks at spaces and only an unbreakable
   token is broken mid-token. */
.rdesc{color:var(--rbp-text-secondary);font-size:.9rem;line-height:1.45;
  margin-top:5px;display:block;max-width:74ch;overflow-wrap:anywhere}

/* THE WAIT, as the largest thing in the row after the identifier. Semantic
   colour, never the link accent: --rbp-age is solved for AA on every surface
   this lands on, which --color-warning (#ffc107, 1.37:1 on white) is not. */
.agebox{display:flex;flex-direction:column;align-items:flex-end;gap:2px;
  text-align:right;white-space:nowrap}
.agenum{font-size:1.45rem;font-weight:650;color:var(--rbp-age);
  font-variant-numeric:tabular-nums;line-height:1;letter-spacing:-.02em}
.agelab{font-size:.64rem;text-transform:uppercase;letter-spacing:.08em;color:var(--rbp-text-muted)}
.rbprow .detail{border-top:1px solid var(--color-border-light);
  background:var(--color-bg-secondary);padding:12px 16px 14px}
.seen{font-size:.7rem;color:var(--rbp-text-muted);text-transform:uppercase;
  letter-spacing:.08em;margin:0 0 6px}
.dl{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;font-size:.84rem;margin:0}
.dl dt{color:var(--rbp-text-muted)}
.dl dd{margin:0}

.more-wrap{display:flex;justify-content:center;margin:18px 0 56px}
.morebtn{border:1px solid var(--color-border);background:var(--color-bg-content);
  color:var(--rbp-link);border-radius:6px;padding:8px 18px;font:inherit;cursor:pointer}
.morebtn:hover,.morebtn:focus-visible{border-color:var(--rbp-link)}
/* THE EMPTY STATE DRAWS THE READER'S OWN TEXT, so it wraps at every width.
   `overflow-wrap: anywhere` was declared for `.mono, code, pre` and only inside
   the 768px media query, and nothing covered this box at all: 400 unbroken
   characters typed into the filter scrolled the document sideways by 2,466px at
   1280 wide, and 80 characters did it at 375. No link is needed to reach it,
   only a pasted package name, and the reader is already looking at "nothing
   matches" when the page starts scrolling. */
.empty{padding:44px 20px;text-align:center;color:var(--rbp-text-muted);
  overflow-wrap:anywhere;
  background:var(--color-bg-content);border:1px solid var(--color-border);border-radius:8px}
.empty b{display:block;color:var(--color-text-primary);font-size:1rem;margin-bottom:6px}

/* ---- the slide-over: seven pages, no navigation ---- */
/* THE MODAL LAYER SITS ABOVE THE HEADER, and it did not.
   
   These were 30 and 31 against a `.header` at z-index 1000, so the site header
   painted OVER the scrim and over the panel. Three consequences, all live:
   
     - the header was never dimmed, so the dialog did not read as modal;
     - its nav links and the theme toggle stayed clickable THROUGH the scrim,
       which is a dialog declaring `aria-modal="true"` while leaving the document
       behind it operable by pointer. The keyboard trap in list.html was added for
       exactly this reason and only covered Tab;
     - the panel's own Close button, sitting in the top-right, was underneath the
       theme toggle. `document.elementFromPoint` at the button returned
       `#themeToggle`, and a Playwright click on it timed out reporting the
       interception, which is how this was found.
   
   1100/1101/1102 rather than 1001: the header's 1000 is the highest number in
   either stylesheet, and a modal layer one greater than the current maximum is the
   kind of margin that gets eaten by the next sticky thing. */
.scrim{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1100}
.panel{position:fixed;top:0;right:0;bottom:0;width:min(560px,100%);z-index:1101;
  background:var(--color-bg-content);border-left:1px solid var(--color-border);
  overflow-y:auto;padding:24px 28px 56px}
.panel h2{font-size:1.08rem;margin:26px 0 8px}
.panel h2:first-of-type{margin-top:8px}
.panel p{font-size:.9rem;color:var(--rbp-text-secondary);max-width:60ch}
.panel blockquote{margin:10px 0;padding:8px 14px;border-left:3px solid var(--color-border);
  color:var(--rbp-text-secondary);font-size:.89rem}
/* FIXED, NOT ABSOLUTE, and this was a live defect.
   
   `.panel` is `position:fixed` with `overflow-y:auto`, so it is its own scroll
   container, and an absolutely-positioned child is placed against the panel's
   padding box INCLUDING the scrolled-away part. Scrolling the panel therefore
   carried the Close button off the top of it, and the panel is 3,528px tall
   against a 900px viewport: past the first screen, the only visible way out of a
   modal dialog was gone. Escape and the scrim still worked, which is exactly why
   nobody noticed -- the keyboard route was fine and the visible one was not.
   
   Found by a Playwright click timing out while reaching for a button that was
   really not there, which no source-level check could have seen.
   
   `fixed` lands in the same place: the panel is itself fixed at top:0 right:0
   with the same width, so these coordinates resolve identically against the
   viewport and now stay put. */
.closebtn{position:fixed;top:18px;right:22px;z-index:1102;
  background:var(--color-bg-content);
  border:1px solid var(--color-border);border-radius:6px;padding:4px 11px;
  cursor:pointer;color:var(--rbp-text-muted);font:inherit;font-size:.82rem}

@media (max-width:900px){
  /* The lead stacks. The distribution is the evidence for the number above it,
     so it stays directly beneath rather than moving below the controls. */
  .lead{grid-template-columns:1fr;gap:22px}
  .dist{border-top-width:1px}
}

@media (max-width:640px){
  /* Two columns, not three: the chevron keeps a lane of its own at the end of
     the first row and the wait takes the width beneath it. Without the explicit
     placement the chevron auto-flowed into the second row and landed beside the
     days count. */
  .rbprow > summary{grid-template-columns:1fr 24px;gap:0 12px}
  .rowbody{grid-column:1}
  .rbprow > summary::after{grid-column:2;grid-row:1}
  .agebox{grid-column:1 / span 2;flex-direction:row;align-items:baseline;
    gap:7px;text-align:left;margin-top:9px}
  .agenum{font-size:1.15rem}
  /* THE UNIT STAYS IN THE LAYOUT AND IN THE ACCESSIBILITY TREE.

     This was `.cmd-count span{display:none}` when the count lived in the command
     bar, which took "reserved, public, unpublished" out of both, so below 640px
     the page's only h1 was the bare string "1,691": no unit, no subject, on the
     viewport where most shared links are opened and to every screen reader on a
     phone. The count is its own block now and the unit sits under it at every
     width, so there is nothing left to hide; the size is the only thing that
     moves. */
  /* Smaller so it still fits on one line: 29 characters at 1.12rem overflows a
     320px viewport, and the whole point of the rule above is that this phrase
     does not break. Measured, not guessed. */
  .cmd-count span{font-size:.92rem}
  .distbar{grid-template-columns:minmax(0,7.5rem) 1fr 3rem;gap:9px}
  .cmd-grow{flex:1 1 100%}
  /* THE SEARCH TAKES ITS OWN LINE AND THE TWO FILTERS SHARE THE NEXT ONE.

     Measured at 375, where the content box is 335px: "Source All" is 104 and the
     age control 220, which came to 336 at the tightened gap and wrapped to a
     third row for one pixel. Capping the select is what actually buys the line
     -- "Under 180 Days" is the longest option and sets at 133px, so 150 is a cap
     the copy clears rather than a cap it ellipsises against, and `text-overflow`
     is already declared above for anything longer that arrives later.

     Below about 350px it wraps again, which is correct: three short rows beat one
     row of controls squeezed to nothing. */
  /* FLUSH AGAIN HERE, and it is a fit rather than a preference. The content box
     is 335px at 375, the two filters need 330 of it to share one line, and 16px
     of inset each way leaves 303 -- so the inset buys breathing room by spending
     a whole third row of controls on the viewport where the row count matters
     most. Nothing is crowded against a surface edge at this width anyway: the
     bar is the full column and the controls fill it. */
  .cmd-in{gap:10px 12px;padding-inline:0}
  /* The grouping margin is a horizontal relationship, and there is no longer one
     to express: the search has the line above and the two filters start this one
     together. Left in, it indents the pair by 18px against every other control
     on the page and costs the row the pixels it needs to stay one line. */
  .srcpick{margin-inline-start:0}
  .cmd-lab select{max-width:min(150px,44vw)}
  /* THE POPOVER IS ANCHORED TO THE EDGE THE CONTROL IS ON.

     `right:0` is right on a desktop, where the control sits at the right end of
     the bar and a menu hanging left of it stays on the page. On a phone the
     control is at the LEFT end of its row, so the same rule laid a 341px menu
     from x=-216 to x=124: two thirds of the source list off the side of the
     screen, unreachable, and not even a horizontal scroll to reveal it because
     the overflow is to the left of the origin. */
  .srcmenu{left:0;right:auto;width:min(21rem,calc(100vw - 34px))}
  .panel{padding:20px 18px 48px}
}

/* The evidence chips on the list page: the pill treatment the deleted table's
   source links used, under the class the list template actually emits. */
.where{display:flex;gap:6px;flex-wrap:wrap;margin-top:9px;align-items:center}
.wlab{font-size:.68rem;text-transform:uppercase;letter-spacing:.09em;
  color:var(--rbp-text-muted);margin-right:2px}
a.chip,span.chip{display:inline-block;padding:2px 9px;border-radius:999px;
  font-size:.76rem;white-space:nowrap;text-decoration:none;
  border:1px solid var(--rbp-link);color:var(--rbp-link)}
a.chip:hover,a.chip:focus-visible{background:var(--rbp-link);color:var(--color-bg-content)}
/* A feed with no per-ID page is still a place the ID is showing up. Shown, and
   visibly not a link, rather than dropped. */
span.chip.nolink{border-style:dashed;border-color:var(--color-border);color:var(--rbp-text-muted)}
/* A CHIP HAS TO BE ABLE TO BREAK, because one of them can be a sentence.

   The chips carry the CSAF publisher name now, and the longest real one was
   "Bundesamt fur Sicherheit in der Informationstechnik": 424px of nowrap text
   inside a flex row, which is wider than a 320px viewport and cannot wrap, so it
   set the minimum content width of the whole row and pushed the PAGE sideways by
   128px at 320 and 73px at 375. WCAG 1.4.10.

   That one is drawn as "BSI" since 2026-08-31 (see PUB_SHORT in
   templates/list.html), which is a shorter label rather than a bound: the table
   holds the names this page knows an abbreviation for, and a publisher that is
   not in it renders whole. So this stays, and it is what catches the next long
   name on the run it first appears.

   It did not show while the rows were cards, because `.rbprow` was
   `overflow:hidden` and quietly clipped the name instead. That is the worse of
   the two failures and it was invisible: the reader lost the publisher's name
   with nothing to say it had been cut. Wrapping keeps the name.

   Scoped to the evidence chips rather than applied to `.chip` generally: the
   certainty chips on /method and /policy are single uppercase words that should
   never break, and `.where` is the only place a chip holds a publisher name. */
.where{min-width:0}
@media (max-width:768px){
  .where a.chip,.where span.chip{white-space:normal;overflow-wrap:anywhere}
}


/* A card that holds nothing but prose.
   
   `.card-body > p` is capped at 68ch for readability, which is right, and on
   /method and /policy the cards also hold tables, metric grids, headings and
   lists that use the rest of the width. /about holds fourteen uniform paragraphs
   and nothing else, so the same rule left a 1200px card with 686px of text in it
   and half the card empty: the measure was correct and the container was not.
   
   Applied by name rather than by a `:has(p:only-child)`-style rule, so adding a
   table to a page does not silently change its width. /policy is deliberately NOT
   given this: its lists and blockquotes already fill the space. */
/* CENTRED, and it was not.
   
   The measure was the right fix for the half-empty card it replaced, but with no
   auto margin the card ended at 869px inside a 1,199px container: /about-this-count
   was the only page whose cards stopped two thirds of the way across, with the
   border ending mid-screen and 330px of void beside it. The nav and footer span
   the full width, so it read as a broken layout rather than as a chosen measure.
   
   `margin-inline` rather than `margin: 0 auto`, so it cannot reset the vertical
   margins the card already has. */
.card-prose { max-width: 78ch; margin-inline: auto; }
