  /* ───────────────────────────────────────────────────────────
     detections.org — Stripe-light docs · indigo accent · Geist
     ─────────────────────────────────────────────────────────── */

  :root {
    /* surface */
    --bg:           #fafaf9;
    --bg-elev:      #ffffff;
    --bg-sunken:    #f4f4f1;
    /* ink */
    --ink:          #1a1d24;
    --ink-strong:   #0c0e12;
    --ink-soft:     #56616e;
    /* Was #8b95a3, which measured 2.90:1 against --bg and failed WCAG AA for
       body text. This is 4.67:1 and still reads as the third step down from
       --ink-soft (6.04:1). */
    --ink-faint:    #68727f;
    /* rule */
    --rule:         #e6e6e1;
    --rule-soft:    #ededea;
    /* accent — refined indigo */
    --accent:       #4338ca;
    --accent-hover: #3730a3;
    --accent-soft:  #eef2ff;
    --accent-ring:  rgba(67, 56, 202, 0.18);
    /* severity */
    --sev-critical: #c53030;
    --sev-high:     #c2410c;
    --sev-medium:   #b45309;
    --sev-low:      #15803d;
    --sev-info:     #64748b;
    --sev-critical-bg: #fef2f2;
    --sev-high-bg:     #fff7ed;
    --sev-medium-bg:   #fffbeb;
    --sev-low-bg:      #f0fdf4;
    --sev-info-bg:     #f8fafc;

    --font-sans: "Geist", "Söhne", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, "Menlo", monospace;

    --measure: 1280px;
    --radius: 6px;
    --radius-sm: 4px;

    color-scheme: light;
  }

  /* ─────────── dark ───────────
     Keyed on an attribute, not `prefers-color-scheme`, because light is the
     default here: a visitor whose OS is dark still gets the light site until
     they choose otherwise. The head script resolves the stored preference
     (light | dark | system) to a concrete `data-theme` before first paint, so
     this file never needs the media query and no-JS visitors get light.

     Only the tokens are redefined; nothing below this block knows which theme
     it is in. Every value was checked against the dark background and clears
     WCAG AA for body text — the severity colors are lightened rather than
     reused, because the light set sits at 2-3:1 on a dark ground. */
  :root[data-theme="dark"] {
    --bg:           #0f1216;
    --bg-elev:      #161a20;
    --bg-sunken:    #0a0d10;

    --ink:          #e3e7ed;
    --ink-strong:   #f6f8fa;
    --ink-soft:     #a8b2c0;
    --ink-faint:    #828d9c;

    --rule:         #262c35;
    --rule-soft:    #1d222a;

    --accent:       #9d95f5;
    --accent-hover: #b5aeff;
    --accent-soft:  #1c1a33;
    --accent-ring:  rgba(157, 149, 245, 0.25);

    --sev-critical: #ff8a80;
    --sev-high:     #ffab70;
    --sev-medium:   #f3c265;
    --sev-low:      #7ddc9a;
    --sev-info:     #9aa7b8;
    --sev-critical-bg: #2a1614;
    --sev-high-bg:     #2a1d12;
    --sev-medium-bg:   #272014;
    --sev-low-bg:      #12241a;
    --sev-info-bg:     #171b21;

    color-scheme: dark;
  }

  :root[data-theme="dark"] img,
  :root[data-theme="dark"] .brand-mark { opacity: 0.92; }

  /* ─────────── reset ─────────── */

  *, *::before, *::after { box-sizing: border-box; }
  html, body { margin: 0; padding: 0; }
  body {
    font-family: var(--font-sans);
    font-size: 15px;
    line-height: 1.55;
    color: var(--ink);
    background: var(--bg);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    font-feature-settings: "ss01", "cv11";
  }

  ::selection { background: var(--accent-soft); color: var(--accent-hover); }

  /* Keyboard users land on the nav on every page; without this, reaching the
     results means tabbing past the search field and three links each time.
     Visible only while focused, so it costs sighted users nothing. */
  .skip-link {
    position: absolute;
    left: 0.5rem;
    top: -4rem;
    z-index: 100;
    padding: 0.6em 1em;
    background: var(--accent);
    color: #fff;
    border-radius: var(--radius-sm);
    font-size: 0.88rem;
    font-weight: 500;
    text-decoration: none;
    transition: top 120ms ease;
  }
  .skip-link:focus { top: 0.5rem; color: #fff; text-decoration: none; }

  /* `:focus-visible` rather than `:focus` so a mouse click does not draw a
     ring, but tabbing always does. Several controls set `outline: none` for
     their own focus treatment; this is the floor beneath them. */
  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: 2px;
  }

  /* The staggered reveal is decoration. Honour a stated preference for less
     motion rather than animating anyway — vestibular disorders are the reason
     this media query exists. */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
    .reveal > * { opacity: 1; }
  }

  a {
    color: var(--accent);
    text-decoration: none;
    transition: color 100ms ease;
  }
  a:hover { color: var(--accent-hover); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }

  code, pre { font-family: var(--font-mono); }

  /* ─────────── frame ─────────── */

  /* Column so `main` can absorb the slack on a short page and hold the colophon
     against the bottom of the viewport — a 404 or a zero-result search would
     otherwise leave it floating mid-screen. */
  .frame {
    max-width: var(--measure);
    margin: 0 auto;
    padding: 1.5rem 2rem 2rem;
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
  }

  main { flex: 1 0 auto; }

  nav.top {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: 1.5rem;
    align-items: center;
    padding-bottom: 1.25rem;
    border-bottom: 1px solid var(--rule);
    margin-bottom: 2.5rem;
  }
  nav.top .mark {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 1rem;
    letter-spacing: -0.015em;
    color: var(--ink-strong);
    text-decoration: none;
  }
  nav.top .mark:hover { color: var(--ink-strong); text-decoration: none; }
  nav.top .mark .brand-mark {
    display: block;
    width: 26px;
    height: auto;
    flex-shrink: 0;
  }
  nav.top .mark .brand-stroke {
    fill: none;
    stroke: var(--ink-strong);
    stroke-width: 2;
  }
  nav.top .mark .brand-accent { fill: var(--accent); }
  nav.top .mark .tld { color: var(--ink-faint); font-weight: 400; }

  nav.top .links {
    display: flex;
    align-items: center;
    gap: 1.15rem;
    font-size: 0.875rem;
    font-weight: 500;
    justify-self: end;
  }
  nav.top .links a {
    color: var(--ink-soft);
    text-decoration: none;
  }
  nav.top .links a:hover { color: var(--ink-strong); text-decoration: none; }
  /* The switch is a preference, not a destination — a little more air than the
     gap between links, so it reads as a control sitting beside them. */
  nav.top .links .theme-switch { margin-left: 0.35rem; }

  /* A preference, not navigation — so it reads as a control rather than a
     link. Hidden until the script confirms it can store a choice; offering a
     switch that silently forgets is worse than offering none. */
  .theme-switch {
    display: none;
    gap: 1px;
    padding: 2px;
    background: var(--bg-sunken);
    border: 1px solid var(--rule);
    border-radius: 999px;
  }
  .theme-switch.is-ready { display: inline-flex; }
  .theme-switch button {
    all: unset;
    padding: 0.25em 0.75em;
    font-family: var(--font-sans);
    font-size: 0.76rem;
    font-weight: 500;
    color: var(--ink-faint);
    border-radius: 999px;
    cursor: pointer;
    transition: color 120ms ease, background 120ms ease;
  }
  /* `all: unset` clears the base button styling but not the site's
     `button:hover`, which would fill the segment indigo and leave near-black
     text on it. Both hover states name their own background for that reason. */
  .theme-switch button:hover {
    color: var(--ink);
    background: transparent;
  }
  .theme-switch button[aria-pressed="true"],
  .theme-switch button[aria-pressed="true"]:hover {
    color: var(--accent);
    background: var(--bg-elev);
    box-shadow: 0 1px 2px rgba(12, 14, 18, 0.06);
  }
  .theme-switch button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
  }

  /* ─────────── nav search ─────────── */

  .nav-search {
    width: 100%;
    max-width: 420px;
    justify-self: center;
    margin: 0;
  }
  .nav-search-field {
    display: flex;
    align-items: center;
    height: 36px;
    background: var(--bg-elev);
    border: 1px solid var(--rule);
    border-radius: 8px;
    transition: border-color 140ms ease, box-shadow 140ms ease;
    cursor: text;
  }
  .nav-search-field:hover { border-color: var(--ink-faint); }
  .nav-search-field:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-ring);
  }
  .nav-search-field .icon {
    flex-shrink: 0;
    width: 15px;
    height: 15px;
    margin: 0 0.45rem 0 0.7rem;
    color: var(--ink-faint);
    transition: color 140ms ease;
  }
  .nav-search-field:focus-within .icon { color: var(--accent); }
  .nav-search-field input {
    flex: 1;
    min-width: 0;
    border: 0;
    background: transparent;
    outline: none;
    padding: 0.45rem 0.35rem;
    font-family: var(--font-sans);
    font-size: 0.88rem;
    color: var(--ink-strong);
    letter-spacing: -0.003em;
  }
  .nav-search-field input::placeholder { color: var(--ink-faint); font-weight: 400; }
  .nav-search-field .kbd {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 20px;
    min-width: 20px;
    padding: 0 0.4em;
    margin-right: 0.4rem;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    font-weight: 500;
    color: var(--ink-faint);
    background: var(--bg-sunken);
    border: 1px solid var(--rule);
    border-radius: 4px;
    letter-spacing: 0.02em;
    transition: opacity 140ms ease;
  }
  .nav-search-field:focus-within .kbd { opacity: 0; }

  @media (max-width: 720px) {
    nav.top {
      grid-template-columns: auto auto;
      row-gap: 0.85rem;
    }
    nav.top .mark { grid-column: 1; grid-row: 1; }
    /* Four links plus the switch do not fit one phone-width line beside the
       wordmark, so they wrap upward from the right edge instead of shrinking
       the wordmark or scrolling the page sideways. */
    nav.top .links {
      grid-column: 2;
      grid-row: 1;
      flex-wrap: wrap;
      justify-content: flex-end;
      gap: 0.5rem 1rem;
    }
    .nav-search {
      grid-column: 1 / -1;
      grid-row: 2;
      max-width: none;
      justify-self: stretch;
    }
    .nav-search-field .kbd { display: none; }
  }

  /* Below ~520px the wordmark and four links no longer share a line, and
     wrapping in place leaves "About" orphaned on a line of its own. Give the
     links their own full-width row instead, with the switch dropping to the
     right beneath them once it stops fitting. */
  @media (max-width: 520px) {
    nav.top .links {
      grid-column: 1 / -1;
      grid-row: 3;
      justify-self: stretch;
      justify-content: flex-start;
      gap: 0.6rem 1.1rem;
    }
    nav.top .links .theme-switch { margin-left: auto; }
  }

  /* ─────────── typography ─────────── */

  /* Rule names become headings verbatim, and they are file-derived: the
     longest is a 104-character snake_case string with nowhere to break. At a
     heading's font size that overflows the viewport on a phone and takes the
     whole page sideways with it. */
  h1, h2, h3, h4 { overflow-wrap: break-word; }

  h1 {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: clamp(1.85rem, 3.5vw, 2.4rem);
    line-height: 1.15;
    letter-spacing: -0.022em;
    color: var(--ink-strong);
    margin: 0;
  }
  h2 {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 1.25rem;
    line-height: 1.3;
    letter-spacing: -0.012em;
    color: var(--ink-strong);
    margin: 0 0 0.85rem;
  }
  h3 {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 1rem;
    line-height: 1.35;
    letter-spacing: -0.005em;
    color: var(--ink-strong);
    margin: 0 0 0.6rem;
  }
  h4,
  /* The same small uppercase label, decoupled from the level. Heading level is
     the document outline; this is the visual role. A panel title that is a
     sibling of the page's h2s should say so in the markup and still look like a
     label, which it cannot do while the look is welded to `h4`. */
  .label-head {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 0.74rem;
    line-height: 1.3;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft);
    margin: 0 0 0.6rem;
  }

  p { margin: 0 0 1rem; }
  p:last-child { margin-bottom: 0; }

  .muted { color: var(--ink-soft); }
  .faint { color: var(--ink-faint); }
  .num   { font-variant-numeric: tabular-nums lining-nums; }

  .lead { font-size: 1.05rem; line-height: 1.55; color: var(--ink-soft); max-width: 60ch; }

  /* No measure on prose blocks: a 68ch cap wrapped paragraphs well short of
     their container, which reads as a hard break in the middle of a sentence
     rather than as a deliberate column. Width is the container's job here.

     Which makes wrapping load-bearing. Rule descriptions routinely carry bare
     reference URLs — one is 106 unbroken characters — and a token that cannot
     break forces its box wider than the viewport and scrolls the whole page
     sideways. The old cap only hid this by leaving slack to the right.
     Inherited, so it reaches li and blockquote; `pre` keeps `white-space: pre`
     and is unaffected, which is what we want for code. */
  .prose, .lead { overflow-wrap: break-word; }
  .prose p   { color: var(--ink); }
  .prose ul,
  .prose ol  { padding-left: 1.3em; margin: 0 0 1em; color: var(--ink); }
  .prose li  { margin-bottom: 0.35em; }
  .prose code { font-size: 0.92em; }
  .prose strong { font-weight: 600; color: var(--ink-strong); }

  .prose h3, .prose h4, .prose h5 { margin: 1.6rem 0 0.6rem; }
  /* The site's h4 is an uppercase label. Inside prose it is a subheading. */
  .prose h4 {
    font-size: 0.92rem;
    letter-spacing: -0.005em;
    text-transform: none;
    color: var(--ink-strong);
  }
  .prose h5 {
    font-family: var(--font-sans);
    font-size: 0.86rem;
    font-weight: 600;
    color: var(--ink-soft);
  }

  .prose blockquote {
    margin: 0 0 1rem;
    padding: 0.1rem 0 0.1rem 1rem;
    border-left: 2px solid var(--rule);
    color: var(--ink-soft);
  }
  .prose pre {
    background: var(--bg-elev);
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    padding: 0.8rem 1rem;
    overflow-x: auto;
    font-size: 0.84rem;
  }
  .prose table { margin: 0 0 1rem; }
  .prose hr { border: 0; border-top: 1px solid var(--rule-soft); margin: 1.4rem 0; }

  .prose > *:first-child { margin-top: 0; }
  .prose > *:last-child  { margin-bottom: 0; }

  /* A lead is the page's opening voice, one step softer than body prose, and
     it stays that way on all three pages that use it. Needs to outrank
     `.prose p` because a lead rendered from Markdown arrives wrapped in a
     paragraph, where it used to be a bare text node inheriting from `.lead`. */
  .prose.lead p { color: var(--ink-soft); }
  /* A standalone `.lead` is a short page opener and keeps its measure. Combined
     with `.prose` it is body content — the detection description, which runs to
     real paragraphs — so the 60ch cap comes off there and only there. */
  .prose.lead { max-width: none; }

  /* ─────────── sections ─────────── */

  section { margin: 2.4rem 0; scroll-margin-top: 1.5rem; }
  section > h2 { margin-bottom: 1rem; }

  hr.rule { border: 0; border-top: 1px solid var(--rule); margin: 1rem 0; }

  /* ─────────── badges ─────────── */

  .badges {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    align-items: center;
  }
  .badge {
    display: inline-flex;
    align-items: center;
    gap: 0.35em;
    font-family: var(--font-mono);
    font-size: 0.74rem;
    font-weight: 500;
    letter-spacing: 0;
    padding: 0.22em 0.55em;
    background: var(--bg-elev);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    line-height: 1.3;
    white-space: nowrap;
    text-decoration: none;
  }
  .badge-soft   { background: var(--bg-sunken); color: var(--ink-soft); border-color: transparent; }
  .badge-ghost  { background: transparent; color: var(--ink-soft); border-style: dashed; }
  .badge-accent { color: var(--accent); background: var(--accent-soft); border-color: transparent; }

  a.badge { transition: border-color 100ms ease, color 100ms ease; }
  a.badge:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; }

  /* CVE badges: a superscript marker for an id found by matching text rather
     than a dedicated field, and a second, colored one for a CVE the CVE
     Program's list does not carry. Both still link — the marker explains the
     evidence, it does not withdraw it. */
  .badge .cve-flag { margin-left: 0.1em; font-size: 0.85em; color: var(--ink-faint); }
  .badge .cve-flag-unpublished { color: var(--sev-high); }
  .badge.cve-unpublished { border-color: var(--sev-high); }

  /* ─────────── search results: match snippets, mode banner, empty state ─────────── */
  .snippet {
    margin-top: 0.35em;
    font-size: 0.84rem;
    line-height: 1.5;
  }
  .snippet mark {
    background: var(--accent-soft);
    color: var(--ink);
    border-radius: 2px;
    padding: 0 0.15em;
    font-weight: 500;
  }
  /* A match inside the rule body is quoted source, not prose about the rule.
     The typeface is the whole signal — an "in rule body" badge used to say it
     in words too, which was a label competing with the text it labeled. */
  .snippet-raw {
    font-family: var(--font-mono);
    font-size: 0.76rem;
    letter-spacing: -0.01em;
  }

  .q-mode {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
    margin: 0 0 0.6rem;
    padding: 0.5rem 0.75rem;
    font-size: 0.86rem;
    color: var(--ink-soft);
    background: var(--bg-sunken);
    border-left: 2px solid var(--accent);
    border-radius: var(--radius-sm);
  }
  .q-mode-swap { margin-left: auto; font-size: 0.82rem; }

  .empty-cell { padding: 2.5rem 1.25rem !important; text-align: center; }
  .empty-head { margin: 0 0 0.75rem; color: var(--ink-soft); }
  .empty-widen {
    list-style: none;
    margin: 0 auto;
    padding: 0;
    display: inline-flex;
    flex-direction: column;
    gap: 0.3rem;
    text-align: left;
    font-size: 0.88rem;
  }
  .empty-widen a { color: var(--accent); }
  .empty-widen a:hover { text-decoration: underline; text-underline-offset: 3px; }
  .empty-widen b { font-weight: 600; font-variant-numeric: tabular-nums; }
  .empty-hint { max-width: 46ch; margin: 0 auto; font-size: 0.84rem; }

  /* severity stamp */
  .stamp {
    display: inline-flex;
    align-items: center;
    gap: 0.42em;
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 0.74rem;
    letter-spacing: 0.02em;
    padding: 0.22em 0.55em;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    line-height: 1.3;
  }
  .stamp::before {
    content: "";
    width: 0.5em;
    height: 0.5em;
    background: currentColor;
    border-radius: 50%;
    flex-shrink: 0;
  }
  .stamp-critical      { color: var(--sev-critical); background: var(--sev-critical-bg); }
  .stamp-high          { color: var(--sev-high);     background: var(--sev-high-bg); }
  .stamp-medium        { color: var(--sev-medium);   background: var(--sev-medium-bg); }
  .stamp-low           { color: var(--sev-low);      background: var(--sev-low-bg); }
  .stamp-informational,
  .stamp-unknown       { color: var(--sev-info);     background: var(--sev-info-bg); }

  /* risk score chip */
  .risk-score {
    display: inline-flex;
    align-items: baseline;
    gap: 0.35em;
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 1.1rem;
    line-height: 1;
    letter-spacing: -0.02em;
    color: var(--ink-strong);
    padding: 0.32em 0.65em;
    background: var(--bg-elev);
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    font-variant-numeric: tabular-nums;
  }
  .risk-score sub {
    font-family: var(--font-mono);
    font-weight: 500;
    font-size: 0.62rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    vertical-align: baseline;
  }
  .risk-score.is-high     { color: var(--sev-high);     border-color: rgba(194, 65, 12, 0.3); background: var(--sev-high-bg); }
  .risk-score.is-critical { color: var(--sev-critical); border-color: rgba(197, 48, 48, 0.3); background: var(--sev-critical-bg); }

  /* ─────────── tables ─────────── */

  table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--font-sans);
    font-size: 0.92rem;
  }
  thead th {
    text-align: left;
    font-family: var(--font-sans);
    font-weight: 500;
    font-size: 0.78rem;
    color: var(--ink-soft);
    padding: 0.7rem 0.85rem 0.55rem;
    border-bottom: 1px solid var(--rule);
    background: transparent;
  }
  tbody td {
    padding: 0.85rem;
    border-bottom: 1px solid var(--rule-soft);
    vertical-align: top;
    /* Rule names are file-derived and run long without a space in them —
       `abusing_security_support_provider_and_authentication_packages` is 61
       characters. An unbreakable cell widens the table past the viewport and
       scrolls the page sideways, which is worst on a phone. */
    overflow-wrap: break-word;
  }
  tbody tr { transition: background 100ms ease; }
  tbody tr:hover { background: var(--bg-elev); }
  table code { font-family: var(--font-mono); font-size: 0.85em; color: var(--ink-soft); }
  table a { color: var(--ink); font-weight: 500; }
  table a:hover { color: var(--accent); text-decoration: none; }

  /* Results table. Fixed layout, because with the auto algorithm the header
     widths are only hints: the Detection column's snippets outbid them and
     squeezed Source down to 68px and six wrapped lines. */
  .results-table { table-layout: fixed; }
  .results-table td { overflow-wrap: break-word; }
  .results-table .updated-cell { white-space: nowrap; }
  .results-table .c-source  { width: 12rem; }
  .results-table .c-sev     { width: 9rem; }
  .results-table .c-attack  { width: 14rem; }
  .results-table .c-updated { width: 7rem; }

  /* One line per cell — clipped with an ellipsis rather than wrapped. The
     full value stays readable through the title tooltip on the name and the
     source link, which are the only two cells that ever truncate. */
  .results-table tbody td {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .results-table .tech-list {
    display: inline-flex;
    flex-wrap: nowrap;
    gap: 0.25rem;
  }

  /* The name cell is the one that can run to two lines: the rule name, and —
     while searching — the snippet showing where the term hit. Each clips
     itself, because text-overflow is not inherited and so does not reach
     from the cell into a child block. */
  .results-table .rule-name,
  .results-table td .snippet {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .results-table .rule-name {
    font-weight: 500;
    font-size: 0.95rem;
  }
  .results-table td .snippet {
    margin-top: 0.2em;
    line-height: 1.4;
  }

  /* Below ~880px the five fixed columns leave the Detection column under
     250px, so the row stops being a row: the name takes a full line and the
     metadata wraps beneath it. The header collapses to a sort strip — the
     three sortable columns are the only part of it worth keeping. */
  @media (max-width: 880px) {
    .results-table { display: block; }
    .results-table thead { display: block; border-bottom: 1px solid var(--rule); }
    .results-table thead tr {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      gap: 0.15rem 1rem;
    }
    .results-table thead tr::before {
      content: "Sort";
      font-size: 0.7rem;
      letter-spacing: 0.06em;
      text-transform: uppercase;
      color: var(--ink-faint);
    }
    .results-table thead th { padding: 0.5rem 0; }
    .results-table thead th:nth-child(2),
    .results-table thead th:nth-child(4) { display: none; }

    .results-table tbody { display: block; }
    .results-table tbody tr {
      display: flex;
      flex-wrap: wrap;
      align-items: baseline;
      gap: 0.4rem 0.85rem;
      padding: 0.9rem 0;
      border-bottom: 1px solid var(--rule-soft);
    }
    /* Cards, not cells: nothing is column-aligned here, and a phone has no
       hover to recover an ellipsis with, so the text wraps again. */
    .results-table tbody td,
    .results-table .rule-name,
    .results-table td .snippet {
      display: block;
      padding: 0;
      border: 0;
      white-space: normal;
      overflow: visible;
      text-overflow: clip;
    }
    .results-table tbody td:first-child { flex: 0 0 100%; }
    .results-table .tech-list { flex-wrap: wrap; }
    .results-table .updated-cell { margin-left: auto; }
    .results-table .empty-cell { flex: 0 0 100%; padding: 2rem 0 !important; }
  }

  /* Source cell. Names are long org/repo strings — mono, small, and wrapping
     at the slash or a hyphen. break-word rather than anywhere: anywhere fills
     the line first and splits mid-word ("security_c / ontent"). */
  table a.src-link {
    font-family: var(--font-mono);
    font-size: 0.78rem;
    font-weight: 400;
    color: var(--ink-soft);
    letter-spacing: 0.01em;
    overflow-wrap: break-word;
  }
  table a.src-link:hover { color: var(--accent); }

  thead th a.sort-link {
    color: var(--ink-soft);
    font-weight: 500;
    font-size: inherit;
    text-decoration: none;
    cursor: pointer;
    display: inline-flex;
    align-items: baseline;
    gap: 0.3em;
  }
  thead th a.sort-link:hover { color: var(--ink); }
  thead th a.sort-link.is-active { color: var(--ink); }
  thead th .sort-ind {
    font-size: 0.85em;
    color: var(--accent);
    line-height: 1;
  }

  /* ─────────── filter form ─────────── */

  .filters {
    background: var(--bg-elev);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 1.25rem 1.4rem;
    margin: 1.6rem 0 1.6rem;
  }
  .filters .row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 1rem 1.2rem;
  }
  .filters .row + .row { margin-top: 1.1rem; }
  /* The query is the primary control — one field, full width, above the
     narrowing filters rather than competing with them for a grid cell. */
  .filters .row-search { grid-template-columns: 1fr; }
  .filters label {
    display: block;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--ink-soft);
    margin-bottom: 0.4em;
  }
  .filters input,
  .filters select {
    width: 100%;
    font-family: var(--font-sans);
    font-size: 0.92rem;
    color: var(--ink);
    background: var(--bg);
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    padding: 0.5em 0.7em;
    outline: none;
    appearance: none;
    transition: border-color 100ms ease, box-shadow 100ms ease;
  }
  .filters input::placeholder { color: var(--ink-faint); }
  .filters input:hover,
  .filters select:hover { border-color: var(--ink-soft); }
  .filters input:focus,
  .filters select:focus {
    border-color: var(--accent);
    background: var(--bg-elev);
    box-shadow: 0 0 0 3px var(--accent-ring);
  }
  .filters select {
    background-image: linear-gradient(45deg, transparent 50%, var(--ink-soft) 50%), linear-gradient(135deg, var(--ink-soft) 50%, transparent 50%);
    background-position: calc(100% - 16px) calc(50% + 1px), calc(100% - 11px) calc(50% + 1px);
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    padding-right: 28px;
  }
  .filters .submit-row {
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
    margin-top: 1.1rem;
  }

  /* buttons */
  button, .button {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    font-family: var(--font-sans);
    font-size: 0.86rem;
    font-weight: 500;
    padding: 0.55em 1em;
    background: var(--accent);
    color: white;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    cursor: pointer;
    text-decoration: none;
    transition: background 100ms ease, border-color 100ms ease, color 100ms ease;
  }
  button:hover, .button:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: white;
    text-decoration: none;
  }
  .button-ghost {
    background: transparent;
    color: var(--ink);
    border-color: var(--rule);
  }
  .button-ghost:hover {
    background: var(--bg-elev);
    color: var(--ink-strong);
    border-color: var(--ink-soft);
  }

  /* pagination */
  .pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    margin: 1.6rem 0 0;
    padding-top: 1rem;
    border-top: 1px solid var(--rule);
    font-size: 0.86rem;
    color: var(--ink-soft);
  }
  .pagination.pagination-top {
    margin: 0.4rem 0 1rem;
    padding-top: 0;
    padding-bottom: 0.9rem;
    border-top: none;
    border-bottom: 1px solid var(--rule);
  }
  .pagination .pager {
    display: flex;
    gap: 0.5rem;
  }
  .pagination .pager a {
    font-size: 0.82rem;
    padding: 0.4em 0.85em;
    color: var(--ink);
    background: var(--bg-elev);
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    text-decoration: none;
    transition: border-color 100ms ease, color 100ms ease;
  }
  .pagination .pager a:hover { color: var(--accent); border-color: var(--accent); }

  /* ─────────── code / pre ─────────── */

  pre.raw {
    background: var(--bg-elev);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 1rem 1.2rem;
    font-size: 0.84rem;
    line-height: 1.55;
    overflow: auto;
    max-height: 32em;
    white-space: pre-wrap;
    word-break: break-word;
    color: var(--ink);
  }
  code.inline {
    font-size: 0.86em;
    padding: 0.1em 0.36em;
    background: var(--bg-sunken);
    border-radius: 3px;
    color: var(--ink);
    font-family: var(--font-mono);
  }

  details > summary {
    cursor: pointer;
    font-size: 0.86rem;
    color: var(--ink-soft);
    padding: 0.4em 0;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
  }
  details > summary::before {
    content: "›";
    color: var(--ink-faint);
    display: inline-block;
    transition: transform 120ms ease;
    font-size: 1.2em;
    line-height: 0;
  }
  details[open] > summary::before { transform: rotate(90deg); }
  details > summary:hover { color: var(--accent); }

  /* ─────────── layout helpers ─────────── */

  .stack       { display: flex; flex-direction: column; gap: 0.5rem; }
  .stack-row   { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
  .stack-lg    { gap: 1rem; }
  .stack-bigger { gap: 1.25rem; }

  .grid-2 {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 280px;
    gap: 3rem;
  }
  @media (max-width: 880px) {
    .grid-2 { grid-template-columns: 1fr; gap: 1.5rem; }
    .frame { padding: 1rem 1.2rem 1.5rem; }
  }

  .margin-stack { display: flex; flex-direction: column; gap: 1.5rem; }
  .margin-stack ul {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 0.35rem;
  }
  .margin-stack li { line-height: 1; }

  /* ─────────── reveal animation ─────────── */

  @keyframes rise {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
  }
  .reveal > * { opacity: 0; animation: rise 320ms cubic-bezier(.2,.7,.2,1) forwards; }
  .reveal > *:nth-child(1) { animation-delay: 20ms; }
  .reveal > *:nth-child(2) { animation-delay: 70ms; }
  .reveal > *:nth-child(3) { animation-delay: 120ms; }
  .reveal > *:nth-child(4) { animation-delay: 170ms; }
  .reveal > *:nth-child(5) { animation-delay: 220ms; }
  .reveal > *:nth-child(6) { animation-delay: 270ms; }
  .reveal > *:nth-child(7) { animation-delay: 320ms; }
  .reveal > *:nth-child(n+8) { animation-delay: 370ms; }

  /* ─────────── footer ─────────── */

  footer.colophon {
    margin-top: 3.5rem;
    padding-top: 1.2rem;
    border-top: 1px solid var(--rule);
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    font-size: 0.82rem;
    color: var(--ink-faint);
  }
  footer.colophon a { color: var(--ink-soft); }
  footer.colophon a:hover { color: var(--accent); text-decoration: none; }
  footer.colophon .colophon-links {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem 1.1rem;
    justify-content: flex-end;
  }

  /* The attribution line is a standing legal notice, not navigation — it sits
     below the rule at the smallest size the page uses and stays out of the way. */
  .colophon-note {
    margin: 0.9rem 0 0;
    font-size: 0.76rem;
    line-height: 1.5;
    color: var(--ink-faint);
    max-width: 78ch;
  }
  @media (max-width: 560px) {
    footer.colophon { flex-direction: column; align-items: flex-start; gap: 0.6rem; }
    footer.colophon .colophon-links { justify-content: flex-start; }
  }

  /* stat number used in lead-ins */
  .stat-num {
    font-family: var(--font-sans);
    font-weight: 600;
    font-size: 1.3em;
    letter-spacing: -0.02em;
    color: var(--ink-strong);
    font-variant-numeric: tabular-nums;
  }
