/* styles/wizard.css — Create-component workflow, SBOM editor & components screens.
   Builds on the tokens/classes in the sheets loaded before it (tokens.css onwards). */

/* ===================================================== wizard scaffold */
.wiz{gap:18px;}
.wiz-top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;}

/* — rail layout — */
/* minmax(0,1fr), not 1fr: a bare 1fr track may grow to its content's widest unbreakable
   line, and one long line ("View source" of a single-line JSON SBOM) pushed the step's
   buttons off the right of the screen. Capped, the long line scrolls in its own box. */
.wiz-body-rail{display:grid;grid-template-columns:236px minmax(0,1fr);gap:20px;align-items:start;}
.wiz-rail{display:flex;flex-direction:column;gap:2px;position:sticky;top:74px;}

/* — stepper layout — */
.wiz-stepper{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;gap:6px;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:8px;}
.wiz-body-stepper{display:grid;grid-template-columns:minmax(0,1fr) 288px;gap:18px;align-items:start;}

/* step button — shared by both layouts */
.wizstep{display:flex;align-items:center;gap:11px;text-align:left;background:none;border:1px solid transparent;border-radius:var(--radius-sm);padding:11px 12px;cursor:default;transition:.14s;width:100%;}
.wizstep.is-unlocked{cursor:pointer;}
.wizstep.is-unlocked:hover{background:var(--surface-2);}
.wizstep-dot{width:30px;height:30px;flex:none;border-radius:9px;display:grid;place-items:center;border:1px solid var(--line-2);background:var(--surface);color:var(--text-faint);transition:.15s;}
.wizstep-tx{display:flex;flex-direction:column;gap:3px;min-width:0;}
.wizstep-lab{font:600 13px/1 var(--font);color:var(--text-dim);white-space:nowrap;}
.wizstep-lab em{font-style:normal;color:var(--text-faint);font-weight:500;font-size:11px;}
.wizstep-sub{font:11px/1 var(--font);color:var(--text-faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.wizstep.is-cur{background:var(--accent-soft);border-color:var(--accent-line);}
.wizstep.is-cur .wizstep-dot{background:var(--accent);border-color:var(--accent);color:var(--accent-ink);box-shadow:0 0 0 4px var(--accent-soft);}
.app[data-theme="light"] .wizstep.is-cur .wizstep-dot{color:#fff;}
.wizstep.is-cur .wizstep-lab{color:var(--text);}
.wizstep.is-done .wizstep-dot{background:color-mix(in oklab,var(--ok) 18%,transparent);border-color:var(--ok);color:var(--ok);}
.wizstep.is-done .wizstep-lab{color:var(--text);}

/* panel + footer */
.wiz-panel{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:26px 28px;display:flex;flex-direction:column;min-height:440px;min-width:0;}
.wiz-stepper-layout .wiz-panel{min-height:480px;}
.wiz-foot{display:flex;align-items:center;gap:10px;margin-top:auto;padding-top:22px;border-top:1px solid var(--line);}
.wiz-foot-sp{flex:1;}
.btn:disabled{opacity:.4;cursor:not-allowed;}

/* ===================================================== step form bits */
.wizform{display:flex;flex-direction:column;gap:22px;flex:1;}
.wizform-head h2{font:700 21px/1.15 var(--font-display);letter-spacing:-.01em;color:var(--text);margin:0;}
.wizform-head p{font:14px/1.5 var(--font);color:var(--text-dim);margin:7px 0 0;max-width:62ch;text-wrap:pretty;}
.wizform-head-row{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;}
/* --focus, not --accent. --accent is #49299a in both themes, so this badge's ink on
   its own --accent-soft fill measured 1.56:1 in dark - a label nobody could read in
   the theme the app boots in. --focus is that same #49299a in light (this renders
   byte-identical there) and --accent-3 in dark, which lifts it to 5.09:1. */
.wiz-advanced{font:600 10px/1 var(--font);letter-spacing:.06em;text-transform:uppercase;color:var(--focus);background:var(--accent-soft);border:1px solid var(--accent-line);padding:4px 7px;border-radius:5px;vertical-align:middle;margin-left:8px;}
/* Both of these are read, not glanced at, and both were failing in the light theme:
   raw --sev-high measured 3.56:1 and --text-faint 3.31:1 on the form ground. The
   72%-toward---text mix is the recipe .admin-verify and .tax-flag already use for
   exactly this - a token that carries meaning but was never picked to be read at
   10px - and it lifts "required" to 6.19:1 light / 8.67:1 dark while staying
   unmistakably the warning orange. Deliberately NOT the severity treatment: nobody
   asked for a specific hue here, and a field label has to be legible first. */
.field-req{font:600 10px/1 var(--font);text-transform:uppercase;letter-spacing:.05em;color:color-mix(in oklab,var(--sev-high) 72%,var(--text));margin-left:7px;}
.field-opt{font:11px/1 var(--font);color:var(--text-dim);margin-left:7px;font-weight:500;}

/* category cards */
.catgrid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;}
.catcard{display:flex;flex-direction:column;gap:7px;align-items:flex-start;text-align:left;padding:14px;border:none;border-radius:var(--radius-sm);background:var(--surface);cursor:pointer;transition:.13s;}
.catcard:hover{border-color:var(--line-2);background:var(--surface-2);transform:translateY(-1px);}
.catcard-ic{width:34px;height:34px;border-radius:9px;display:grid;place-items:center;background:var(--surface-2);color:var(--text-dim);transition:.13s;}
.catcard-lab{font:600 13.5px/1 var(--font);color:var(--text);}
.catcard-hint{font:11px/1.3 var(--font);color:var(--text-faint);}
.catcard.is-on{border-color:var(--accent);background:var(--accent-soft);box-shadow:0 0 0 1px var(--accent);}
.catcard.is-on .catcard-ic{background:var(--accent);color:var(--accent-ink);}
.app[data-theme="light"] .catcard.is-on .catcard-ic{color:#fff;}

/* ===================================================== upload / dropzone */
.drop{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;text-align:center;
  border:1.5px dashed var(--line-2);border-radius:var(--radius);background:var(--surface-2);padding:46px 28px;transition:.15s;}
.drop.is-drag{border-color:var(--accent);background:var(--accent-soft);}
.drop.is-parsing{border-style:solid;}
.drop-ic{width:56px;height:56px;border-radius:14px;display:grid;place-items:center;background:var(--surface-3);color:var(--accent);margin-bottom:4px;}
.drop strong{font:600 16px/1.2 var(--font);color:var(--text);}
.drop-sub{font:13px/1.4 var(--font);color:var(--text-dim);}
.drop-sub b{color:var(--text);font-weight:600;}
.drop-actions{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap;justify-content:center;}
.drop-spin{width:26px;height:26px;border-width:3px;}

/* parsed summary */
.sbomsum{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;}
.sbomsum-stat{display:flex;flex-direction:column;gap:4px;padding:14px 16px;background:var(--surface);}
.sbomsum-stat b{font:700 22px/1 var(--font-display);color:var(--text);}
.sbomsum-stat b.is-warn{color:var(--sev-high);}
.sbomsum-stat span{font:11.5px/1.2 var(--font);color:var(--text-faint);}
.sbomsum-stat-wide{grid-column:span 1;}
.sbomsum-stat-wide b{font:600 15px/1.2 var(--font);}
.sbom-metaline{display:flex;align-items:center;gap:16px;flex-wrap:wrap;font:11.5px/1.2 var(--font);color:var(--text-faint);}
.sbom-metaline span{display:flex;align-items:center;gap:6px;}
.sbom-metaline .ic{color:var(--ok);}
.sbom-metaline .mono{color:var(--text-dim);}

/* inventory preview list */
.liblist{display:flex;flex-direction:column;border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;}
.liblist-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:var(--surface-2);font:600 11px/1 var(--font);text-transform:uppercase;letter-spacing:.05em;color:var(--text-faint);}
/* --focus, not --accent: --accent is #49299a in both themes and all but vanishes on the
   dark card, which hid "View source" in the theme most users run. */
.liblist-raw{background:none;border:none;color:var(--focus);font:600 11.5px/1 var(--font);cursor:pointer;text-transform:none;letter-spacing:0;}
.liblist-raw:hover{text-decoration:underline;}
.liblist-raw:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:3px;}
.librow{display:flex;align-items:center;gap:12px;padding:10px 14px;border-top:1px solid var(--line);}
.liblist-flat .librow:first-child,.liblist .librow:first-child{border-top:none;}
.liblist-flat{border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;}
.librow.is-flagged{background:color-mix(in oklab,var(--sev-high) 6%,transparent);}
/* A flagged row is a link to that library's own CVE list; a clean row is inert.
   The button resets its own chrome and re-declares the divider .librow draws. */
button.librow{width:100%;border:0;border-top:1px solid var(--line);background:none;font:inherit;color:inherit;text-align:left;cursor:pointer;}
button.librow:hover,button.librow:focus-visible{background:color-mix(in oklab,var(--sev-high) 14%,transparent);}
.librow-chev{color:var(--text-faint);flex:none;}
.librow-name{font:600 12.5px/1 var(--font-mono);color:var(--text);flex:1;min-width:0;}
.librow-ver{color:var(--text-faint);font-weight:500;}
.librow-sup{font:11.5px/1 var(--font);color:var(--text-dim);width:130px;flex:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.librow-type{font:10.5px/1 var(--font-mono);color:var(--text-faint);background:var(--surface-2);padding:4px 7px;border-radius:5px;flex:none;}
.librow-cve{display:inline-flex;align-items:center;gap:4px;font:600 11px/1 var(--font);color:var(--sev-high);background:color-mix(in oklab,var(--sev-high) 14%,transparent);border:1px solid color-mix(in oklab,var(--sev-high) 30%,transparent);padding:4px 7px;border-radius:20px;flex:none;}
.librow-ok{display:grid;place-items:center;color:var(--ok);opacity:.7;flex:none;width:22px;}
/* Match still running (or failed): the status cell must show neither a tick nor a
   count, because both would be a claim we cannot yet support. */
.librow-pending{display:grid;place-items:center;color:var(--text-faint);flex:none;width:22px;}
.liblist-more{padding:9px 14px;border-top:1px solid var(--line);font:12px/1 var(--font);color:var(--text-faint);background:var(--surface-2);}
/* "Showing 7 of 89 · Show 50 more · Show all 89 · Show fewer": the count left, the actions right, wrapping on a phone. */
.liblist-more-row{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px 16px;line-height:1.4;}
.liblist-more-actions{display:flex;flex-wrap:wrap;gap:4px 14px;}
.liblist-more-actions .liblist-raw{padding:2px 0;}
/* The SBOM laid out (sbomSourceView). Long values (paths, descriptions, hashes) wrap
   inside the box rather than need sideways scrolling; each line keeps its indent. */
.sbom-raw{margin:0;padding:14px;max-height:420px;overflow:auto;font-size:11px;line-height:1.55;color:var(--text-dim);background:var(--bg-panel);border-top:1px solid var(--line);white-space:pre-wrap;overflow-wrap:anywhere;tab-size:2;}

/* ===================================================== SBOM editor */
.sbed{display:flex;flex-direction:column;gap:0;border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;}
.sbed-bar{display:flex;align-items:center;gap:12px;padding:12px 14px;background:var(--surface-2);border-bottom:1px solid var(--line);}
.sbed-search{flex:1;height:36px;min-width:160px;}
.sbed-meta{display:flex;align-items:center;gap:12px;}
.sbed-count{font:12px/1 var(--font);color:var(--text-dim);}
.sbed-count b{color:var(--text);font-weight:700;}
.sbed-flag{display:inline-flex;align-items:center;gap:5px;font:600 11.5px/1 var(--font);color:var(--sev-high);}
.sbed-table{display:flex;flex-direction:column;max-height:392px;overflow:auto;}
.sbed-row{display:grid;grid-template-columns:26px 1fr 96px 132px 132px 92px 34px;align-items:center;gap:10px;padding:7px 14px;border-top:1px solid var(--line);}
.sbed-head{position:sticky;top:0;z-index:2;background:var(--surface-2);border-top:none;border-bottom:1px solid var(--line);
  font:600 10px/1 var(--font);text-transform:uppercase;letter-spacing:.05em;color:var(--text-faint);padding-top:10px;padding-bottom:10px;}
.sbed-row.is-flagged{background:color-mix(in oklab,var(--sev-high) 5%,transparent);}
.sbed-grip{color:var(--text-faint);opacity:.5;display:grid;place-items:center;cursor:grab;}
.sbed-cell-name{display:flex;flex-direction:column;gap:2px;min-width:0;}
.sbed-purl{font-size:10px;color:var(--text-faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.cellinput{width:100%;border:1px solid transparent;background:transparent;color:var(--text);font:13px/1 var(--font);padding:6px 8px;border-radius:6px;outline:none;transition:.12s;}
.cellinput:hover{background:var(--surface-2);}
.cellinput:focus{border-color:var(--accent);background:var(--surface);box-shadow:0 0 0 2px var(--accent-soft);}
.cellinput-strong{font-weight:600;font-family:var(--font-mono);font-size:12.5px;}
.cellinput.mono{font-family:var(--font-mono);font-size:12px;}
.cellselect{cursor:pointer;appearance:none;-webkit-appearance:none;color:var(--text-dim);}
.sbed-status{display:flex;justify-content:flex-start;}
.sbed-cve{display:inline-flex;align-items:center;gap:4px;font:600 10.5px/1 var(--font);color:var(--sev-high);background:color-mix(in oklab,var(--sev-high) 14%,transparent);border:1px solid color-mix(in oklab,var(--sev-high) 28%,transparent);padding:4px 6px;border-radius:5px;white-space:nowrap;}
.sbed-clean{display:inline-flex;align-items:center;gap:4px;font:500 10.5px/1 var(--font);color:var(--text-faint);}
.sbed-del{background:none;border:none;color:var(--text-faint);cursor:pointer;display:grid;place-items:center;padding:5px;border-radius:6px;}
.sbed-del:hover{color:var(--sev-critical);background:var(--surface-2);}
.sbed-empty{display:flex;flex-direction:column;align-items:center;gap:8px;padding:36px;color:var(--text-faint);font:13px/1 var(--font);}

/* ===================================================== match step */
/* As many columns as the panel has room for, none under 200px; a name too long for its
   card ends in an ellipsis. repeat(4,1fr) let a column grow to its longest name, and with
   the live list (80 suppliers) the grid outgrew the panel in any window under ~1600px. */
.suppgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(200px,100%),1fr));gap:10px;}
.suppcard{position:relative;display:flex;align-items:center;gap:10px;text-align:left;padding:11px 12px;border:none;border-radius:var(--radius-sm);background:var(--surface);cursor:pointer;transition:.13s;}
.suppcard:hover{background:var(--surface-2);}
.suppcard-av{width:30px;height:30px;flex:none;border-radius:8px;display:grid;place-items:center;font:700 11px/1 var(--font-mono);background:var(--surface-2);color:var(--text-dim);}
.suppcard-name{font:600 13px/1.1 var(--font);color:var(--text);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.suppcard.is-on{border-color:var(--accent);background:var(--accent-soft);box-shadow:0 0 0 1px var(--accent);}
.suppcard.is-on .suppcard-av{background:var(--accent);color:var(--accent-ink);}
.app[data-theme="light"] .suppcard.is-on .suppcard-av{color:#fff;}
.suppcard-sug{position:absolute;top:-8px;right:8px;display:inline-flex;align-items:center;gap:3px;font:600 9.5px/1 var(--font);letter-spacing:.02em;color:var(--accent-ink);background:var(--accent);padding:3px 6px;border-radius:20px;}
.app[data-theme="light"] .suppcard-sug{color:#fff;}
.brandchips{display:flex;flex-wrap:wrap;gap:8px;}
.chip-brand{padding:8px 13px;}
.chip-brand.is-on{background:var(--accent-soft);border-color:var(--accent-line);color:var(--text);}
.chip-brand.is-on .ic{color:var(--accent);}

/* ===================================================== review (hero) */
.rev-hero{display:flex;align-items:center;justify-content:space-between;gap:22px;flex-wrap:wrap;padding:22px 24px;border-radius:var(--radius);
  background:linear-gradient(100deg,var(--accent-soft),transparent 72%),var(--surface);border:1px solid var(--accent-line);}
.rev-hero.is-clean{background:var(--surface);border-color:var(--line);}
.rev-hero-l{display:flex;align-items:center;gap:16px;}
.rev-hero-ic{width:46px;height:46px;flex:none;border-radius:13px;display:grid;place-items:center;background:var(--accent);color:var(--accent-ink);box-shadow:0 0 24px var(--accent-soft);}
.app[data-theme="light"] .rev-hero-ic{color:#fff;}
.rev-hero-num{font:700 34px/1 var(--font-display);letter-spacing:-.02em;color:var(--text);display:flex;align-items:baseline;gap:11px;}
.rev-hero-num span{font:600 15px/1.2 var(--font);color:var(--text-dim);letter-spacing:0;}
.rev-hero-sub{font:13px/1.4 var(--font);color:var(--text-dim);margin:6px 0 0;}
.rev-hero-sev{display:flex;flex-wrap:wrap;gap:8px;}
.rev-sevchip{display:inline-flex;align-items:center;gap:7px;font:600 12px/1 var(--font);color:var(--sc);background:color-mix(in oklab,var(--sc) 13%,transparent);border:1px solid color-mix(in oklab,var(--sc) 28%,transparent);padding:7px 11px;border-radius:20px;}
.rev-sevchip-dot{width:7px;height:7px;border-radius:50%;background:var(--sc);box-shadow:0 0 7px var(--sc);}

.rev-cols{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:18px;align-items:start;}
/* The review's two columns need about 880px of PANEL: the match rows' fixed columns
   (~500px) beside the 300px summary. The side navigation and the step rail take their
   share of the window first, so a 1360px window left too little and the summary card
   ran off the right edge. Decided by the panel's own width, not the window's. */
.wiz-panel{container-type:inline-size;container-name:wizpanel;}
@container wizpanel (max-width: 880px){
  .rev-cols{grid-template-columns:minmax(0,1fr);}
}
.rev-sec-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:13px;}
.rev-sec-head h3{font:600 15px/1 var(--font);color:var(--text);margin:0;}
/* ===================================================== review (matches list)
   Converges on the .ent-row grammar (browse.css) — grid row, ellipsis description,
   hover, chevron. (The component detail list has since moved to fixed-track rows
   sized by container query; see .mvl-* at the end of this file.)

   Colour budget is two things: the severity rail and the CVSS numeral. Both are
   tinted per band via color-mix toward --text, which raises contrast against the
   surface in BOTH themes with one declaration — no hex, no theme fork, no opacity
   ramp. Measured worst case: rail 4.22:1 (dark Critical), numeral 5.20:1. */

.rev-list{display:flex;flex-direction:column;background:var(--surface);
  border-radius:var(--radius-sm);overflow:hidden;
  /* Fallback --sc for a band with no .sev-* rule (SEV_ORDER is DB-driven, so an
     unrecognised band is possible). Set on the PARENT so it arrives by inheritance:
     any .sev-* class on .rev-item then wins regardless of stylesheet order. */
  --sc:var(--text-faint);}

.rev-item{position:relative;border-top:1px solid var(--line);}
.rev-item:first-child{border-top:none;}
.rev-item.is-open{background:var(--surface-2);}
.rev-item::before{content:"";position:absolute;left:0;top:0;bottom:0;width:4px;
  background:var(--sc);}

.rev-row{display:grid;grid-template-columns:84px 42px 118px 1fr 148px 14px;
  align-items:center;gap:13px;width:100%;
  padding:7px 14px 7px 12px;border:none;background:none;color:inherit;font:inherit;
  text-align:left;cursor:pointer;transition:background .1s;}
.rev-row:hover{background:var(--surface-2);}
.rev-row:focus-visible{outline:2px solid var(--accent-line);outline-offset:-2px;}

/* The severity band in its OWN colour — Critical is red, High orange, and so on.
   Deliberately the same recipe as .sevtag (tags.css): raw var(--sc) text on a 13%
   tint with a 26% border, so this list matches every other severity pill in the app.
   Raw, not mixed toward --text: measured against each theme's own surface the raw
   token is 5.44:1 dark / 6.47:1 light for Critical — mixing only washed the red out. */
.rev-sev{justify-self:start;display:inline-block;max-width:100%;
  padding:2px 7px;border-radius:5px;
  font:600 9.5px/1.5 var(--font);letter-spacing:.05em;text-transform:uppercase;
  color:var(--sc);
  background:color-mix(in oklab,var(--sc) 13%,transparent);
  border:1px solid color-mix(in oklab,var(--sc) 26%,transparent);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* Right-aligned tabular figures so 7.5 / 9.8 / 10.0 form a rankable column, in the
   same band colour so the number and the chip read as one unit. */
.rev-cvss{justify-self:end;font:600 12.5px/1 var(--font-mono);
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum";
  color:var(--sc);white-space:nowrap;}
/* Unscored: an em dash, not a number. The band is still named in the chip beside it,
   so nothing is lost — see the Severity/CVSS rows in the disclosed detail. */
.rev-cvss-none{color:var(--text-faint);font-weight:400;}

.rev-id{font:600 12px/1 var(--font-mono);color:var(--text);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

/* Upstream CVE prose is the least actionable thing on this screen: one faint line,
   truncated at a glyph boundary with a real ellipsis rather than mid-word. */
.rev-desc{min-width:0;font:12px/1.35 var(--font);color:var(--text-faint);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

.rev-lib{display:flex;align-items:baseline;min-width:0;
  font:11.5px/1.3 var(--font);color:var(--text-dim);white-space:nowrap;}
.rev-lib-name{min-width:0;overflow:hidden;text-overflow:ellipsis;
  font:500 11.5px/1.3 var(--font-mono);}
/* The separator lives here, on the version. No version element, no "@". */
.rev-lib-ver{flex:none;max-width:56px;overflow:hidden;text-overflow:ellipsis;
  font:500 11.5px/1.3 var(--font-mono);color:var(--text-faint);}
.rev-lib-ver::before{content:"@";color:var(--text-faint);}
/* Neutral, not a severity token: this is a data-quality state, and borrowing
   --sev-medium would mislabel the row. */
.rev-lib-nover{flex:none;margin-left:6px;padding:1px 5px;border-radius:4px;
  font:italic 400 10.5px/1.35 var(--font);color:var(--text-faint);
  border:1px dashed var(--line-2);}

.rev-chev{color:var(--text-faint);opacity:.5;transition:transform .12s,opacity .12s;}
.rev-row:hover .rev-chev,.rev-row:focus-visible .rev-chev{opacity:1;}
.rev-item.is-open .rev-chev{opacity:1;transform:rotate(180deg);}

/* ---- disclosed detail: provenance first, prose last ---- */
.rev-detail{display:flex;flex-direction:column;gap:11px;padding:2px 16px 14px 16px;}
.rev-detail-meta{display:flex;flex-wrap:wrap;gap:10px 24px;margin:0;}
.rev-detail-meta>div{display:flex;flex-direction:column;gap:3px;min-width:0;}
.rev-detail-meta dt{font:600 9.5px/1 var(--font);letter-spacing:.07em;
  text-transform:uppercase;color:var(--text-faint);}
.rev-detail-meta dd{margin:0;font:12px/1.35 var(--font);color:var(--text-dim);
  overflow-wrap:anywhere;}
.rev-detail-wide{flex-basis:100%;}
.rev-detail-none{color:var(--text-faint);font-style:italic;}
.rev-detail-note{margin:0;padding:8px 11px;border-radius:var(--radius-sm);
  background:var(--surface-2);border:1px solid var(--line);
  font:11.5px/1.5 var(--font);color:var(--text-dim);max-width:78ch;}
.rev-detail-desc{margin:0;max-width:78ch;font:12.5px/1.6 var(--font);color:var(--text-dim);}
.rev-summary{background:var(--surface);border:none;border-radius:var(--radius);padding:18px;}
.rev-card-name{font:600 16px/1.25 var(--font);color:var(--text);}
.rev-card-cat{margin:10px 0 14px;}
.rev-meta{display:flex;flex-direction:column;gap:2px;border-top:1px solid var(--line);padding-top:12px;}
.rev-brands-lab{font:600 11px/1 var(--font);text-transform:uppercase;letter-spacing:.05em;color:var(--text-faint);margin:14px 0 8px;}
.rev-brands{display:flex;flex-wrap:wrap;gap:6px;}

/* ===================================================== live summary (stepper) */
.wiz-summary{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:18px;position:sticky;top:74px;display:flex;flex-direction:column;gap:11px;}
.wiz-summary-head{font:600 11px/1 var(--font);text-transform:uppercase;letter-spacing:.06em;color:var(--text-faint);}
.wiz-sum-row{display:flex;align-items:center;justify-content:space-between;gap:12px;font:13px/1.2 var(--font);color:var(--text-dim);}
.wiz-sum-row b{color:var(--text);font-weight:600;text-align:right;max-width:60%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.wiz-sum-div{height:1px;background:var(--line);margin:3px 0;}
.wiz-sum-match{display:flex;flex-direction:column;align-items:center;gap:2px;padding:14px;border-radius:var(--radius-sm);background:var(--accent-soft);border:1px solid var(--accent-line);}
.wiz-sum-match-num{font:700 30px/1 var(--font-display);color:var(--accent);}
.wiz-sum-match-lab{font:11.5px/1 var(--font);color:var(--text-dim);}

/* ===================================================== components screen */

/* ---- the Components inventory card -------------------------------------
   Everything below is scoped to .compcard, which appears on exactly one element
   in the repo (the Components grid card). .entcard / .entcard-* are shared with
   Browse-by-company, which keeps the original look. */

/* Component names are underscore-joined build ids (ePTO_Application_refudrive).
   Underscore has no line-break opportunity, so a long name painted over the
   chevron and out past the card's padding. Break anywhere, clamp to two lines,
   and RESERVE both so every card's stats row starts at the same height. Two
   lines rather than one: this page's near-duplicates differ only in the tail
   that a one-line ellipsis would eat. */
.compcard .entcard-name{font:600 16.5px/1.2 var(--font-display);letter-spacing:-.01em;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;overflow-wrap:anywhere;min-height:40px;}
.compcard .entcard-id{gap:4px;}
.compcard-idline{display:flex;align-items:center;gap:8px;min-width:0;}
/* nowrap + ellipsis still come from .entcard-meta. */
.compcard-idline .entcard-meta{flex:1;min-width:0;color:var(--text-dim);}
/* Bordered rather than a bare fill: --surface-2 on the white card is 1.13:1, so
   without an edge this reads as loose text in light theme. */
.compcard-cvss{flex:none;font:500 10.5px/1 var(--font-mono);font-feature-settings:"tnum";color:var(--text);background:var(--surface-2);border:1px solid var(--line);border-radius:5px;padding:3px 6px;white-space:nowrap;}

/* Two stats, one hero. The three shared .entcard-stat numbers were all 700 19px
   while the component NAME was only 600 15px — the numbers were larger and
   heavier than the identity of the thing they described, and the largest of them
   (max CVSS) reads 10.0 on most cards. Max CVSS is now the tag above; what is
   left is the match count and the SBOM size.
   The hierarchy is carried by SIZE, not by colour: --text-dim against --text is
   a 1.21:1 step on dark (invisible) but 2.8:1 on light, so a colour-based demotion
   would produce two different hierarchies in the two themes. */
.compcard .entcard-stats{grid-template-columns:auto auto;justify-content:start;gap:28px;align-items:end;}
.compcard .entcard-stat b{font:700 16px/1 var(--font-display);font-feature-settings:"tnum";color:var(--text);}
.compcard .entcard-stat span{font:600 10.5px/1 var(--font);text-transform:uppercase;letter-spacing:.06em;color:var(--text-dim);}
/* 26px/700 display is exactly .comp-strip .comp-stat-hero b in the component DETAIL
   page's header strip, so the list hero and the detail hero are one type. */
.compcard .entcard-stat-hero b{font-size:26px;letter-spacing:-.02em;}

/* The legend is the bar's key, in four FIXED cells. Before, it was flex-wrapped
   and filtered to sev>0, so three things were wrong at once: the Critical count
   sat at a different x on every card, "0 Critical" was indistinguishable from
   "Critical omitted", and a four-severity card wrapped to two lines while its
   three-severity neighbour did not — a direct cause of the ragged row heights.
   Four fixed cells fix all three and column-align the counts down the whole grid,
   which is the scan someone actually runs across a dozen ECU builds.
   minmax(0,1fr) rather than 1fr: a 1fr track floors at min-content, so a long
   label would push the grid past the card at the 280px column. */
.compcard .entcard-legend{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;align-items:start;}
.compcard .entcard-leg{display:block;min-width:0;gap:0;padding-top:6px;border-top:3px solid var(--line);}
.compcard .entcard-leg.sev-critical{border-top-color:var(--sev-critical);}
.compcard .entcard-leg.sev-high{border-top-color:var(--sev-high);}
.compcard .entcard-leg.sev-medium{border-top-color:var(--sev-medium);}
.compcard .entcard-leg.sev-low{border-top-color:var(--sev-low);}
/* 13px keeps this legibly below the 26px hero — it is the bar's key, not a second
   chart competing with it. tnum so the counts align down the column. */
.compcard .entcard-leg b{display:block;font:700 13px/1 var(--font-display);font-feature-settings:"tnum";color:var(--text);}
.compcard .entcard-leg span{display:block;margin-top:4px;font:11px/1 var(--font);color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
/* A zero is real information — it is what separates "0 Critical" from "Critical
   omitted" — so it stays readable and only drops its hue and its weight. Last in
   the block on purpose: it ties the .sev-* rules above and wins on order. */
.compcard .entcard-leg.is-zero{border-top-color:var(--line);}
.compcard .entcard-leg.is-zero b{font-weight:500;color:var(--text-dim);}
/* On a capped card the four counts are a partition of the ceiling, not of the
   truth, and because the server takes the top N by CVSS it is the LOW bucket that
   was cut. Muting them stops four confident numbers from contradicting the "+"
   the hero above them just used to admit the count is a floor. */
.compcard.is-capped .entcard-leg b{color:var(--text-dim);}

/* .ent-grid rows stretch and .entcard is a column flex box with nothing claiming
   the slack, so this hairline floated at a different height in every card of a
   row, with dead space beneath it. margin-top:auto absorbs every source of
   internal variance at once — the legend, the clean-card branch, a wrapped name —
   and pins the footer to the card floor. Auto margins take only the space left
   after gap, so .entcard's 14px still holds as the minimum. */
.compcard-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;border-top:1px solid var(--line);padding-top:12px;margin-top:auto;font:11.5px/1.2 var(--font);color:var(--text-dim);}
.compcard-oem{display:flex;align-items:baseline;gap:6px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;color:var(--text);}
.compcard-foot-lab{flex:none;font-weight:400;color:var(--text-dim);}
/* font-feature-settings must come after the font: shorthand, which resets it. */
.compcard-added{flex:none;font:400 10.5px/1.2 var(--font-mono);font-feature-settings:"tnum";color:var(--text-dim);}
/* A clean card swaps roughly 60px of bar + legend for one 12px line. Reserving
   that height keeps its footer on the same baseline as the loaded cards it shares
   a row with, instead of opening a hole above the pinned hairline. */
.compcard-clean{display:flex;align-items:center;gap:7px;min-height:46px;font:600 12px/1 var(--font);color:var(--ok);}
/* ---- the Components list's control strip -------------------------------
   .page is a 20px-gap column, so head -> toolbar -> filterbar -> grid were four
   equal bands and nothing said that the search and the filters are ONE surface
   acting on the grid. 10px inside, then a hairline and 14px out. Components-only
   on purpose: the cards below have no container edge of their own, so the
   controls have to draw the boundary. The Ledger's filter bar sits on a card that
   already draws one, which is why this is not applied there. */
.comp-controls{display:flex;flex-direction:column;gap:10px;padding-bottom:14px;border-bottom:1px solid var(--line);}
.comp-controls .filterbar{padding-bottom:0;}
/* .search is flex:1 and .tool-spacer is also flex:1, so the search box was
   getting exactly half the toolbar and the other half was empty — with a 60-char
   placeholder that truncated mid-phrase below about 1200px. The Ledger's spacer
   earns its keep (two dropdowns, a short placeholder); here it was throwing away
   half the width of the control the page is opened to use. */
.comp-controls .tool-spacer{display:none;}
/* Marker per card, reason once. The "+" is on the capped cards; the explanation
   lives here rather than as an 11px caption repeated on most of the grid (which
   would also make the tallest card in each row taller). Negative margin on the
   BOTTOM: this belongs to the grid it describes, not to the control block whose
   new hairline it would otherwise read as a caption of. */
.comp-capnote{margin:0 0 -8px;font:11.5px/1.4 var(--font);color:var(--text-dim);}

@media (max-width:560px){
  /* Single column here, so names never wrap and the reserved second line is dead
     space on the viewport with the least to spare. Keep the clamp as a guard. */
  .compcard .entcard-name{min-height:0;}
  /* Four equal columns stretched across a full-width card leaves big gutters;
     pack to content width instead. */
  .compcard .entcard-legend{grid-template-columns:repeat(4,max-content);gap:6px 18px;}
}

/* ===================================================== component detail (frame) */
/* Everything below is scoped to .comp-detail, the root of the component and library
   detail pages, or to classes only those two pages render (.comp-head, .comp-strip*,
   .comp-attr*). detail.css is shared with the vulnerability page, which must not
   move, so nothing here touches a base rule. */

/* A bare 1fr is minmax(auto,1fr): its floor is the widest unbreakable child's
   min-content, so one long SBOM filename or a no-wrap row pushed the main column
   past the viewport (194px of sideways scroll at 420). minmax(0,…) lets it shrink
   and the children ellipsise or wrap instead. (0,2,0) beats responsive.css's
   1180px single-column rule, hence the repeat below. */
.comp-detail .detail-grid{grid-template-columns:minmax(0,1fr) 332px;}
.comp-detail .detail-main,.comp-detail .detail-side{min-width:0;}
@media (max-width:1180px){.comp-detail .detail-grid{grid-template-columns:minmax(0,1fr);}}
/* On phones responsive.css turns the rail back into a column but leaves its 1180px
   flex-wrap:wrap on. A wrapping column sizes its line to the widest card's
   max-content, so a long evidence file name stretched every card to 684px (276px of
   sideways scroll in a big SBOM). One line = the rail's own width, and names ellipsise. */
@media (max-width:560px){.comp-detail .detail-side{flex-wrap:nowrap;}}
/* Section heads carry a title, a kicker and up to two buttons; on a narrow card they
   fold to a second line instead of squeezing the title to nothing. */
.comp-detail .dsec-head{flex-wrap:wrap;row-gap:10px;}
.comp-detail .dsec-head-aside{flex-wrap:wrap;justify-content:flex-end;}
/* No generic .btn:focus-visible exists (only Administration's scoped ones), so the
   keyboard ring here was whatever the browser felt like. --focus, never raw --accent: that is 1.5:1 on the dark card. */
.comp-detail .back:focus-visible,.comp-detail .btn:focus-visible{outline:2px solid var(--focus);outline-offset:2px;}
/* A section heading takes focus (tabIndex -1) after a keyboard page turn; the ring
   shows where the next Tab starts. A pointer turn gets none (:focus-visible). */
.comp-detail .dsec-head h3:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:2px;}
/* Phones: the section-head buttons and the status chips are 30 and 28px tall, under
   a comfortable thumb; 34 matches the pager squares and the filter field. */
@media (max-width:560px){
  .comp-detail .dsec-head .btn-sm{min-height:34px;}
  .comp-detail .mvl-filters .admin-filter{height:32px;}
}

/* Header. The title breaks at the <wbr> after each "_" first, and anywhere only as a
   last resort; balance keeps a two-line name from leaving one word on line 2. */
.comp-head .detail-id{line-height:1.1;overflow-wrap:anywhere;text-wrap:balance;}
/* Tags ellipsise their VALUE inside the viewport; the key and the icon never shrink.
   The title attribute on each tag carries the full text. */
.comp-head .dtag{max-width:100%;min-width:0;}
.comp-head .dtag>.ic{flex:none;}
.comp-head .comp-tag-k{flex:none;color:var(--text-faint);}
.comp-head .comp-tag-v{min-width:0;overflow:hidden;text-overflow:ellipsis;}
.comp-head .comp-tag-k + .comp-tag-v{color:var(--text);font-weight:600;}
/* The purl tag is "dtag mono", but .dtag's font shorthand (detail.css, later in the
   cascade) beat tokens.css's .mono, so it rendered in the sans face. */
.comp-head .dtag.mono{font-family:var(--font-mono);font-size:11.5px;}
/* Light only: the kicker line is --text-faint on the --bg canvas, 4.34:1 at 11.5px,
   and it now carries the "updated" date a --text-dim tag used to (5.05:1). --text-dim
   is 4.65:1 here. Dark is 8.02:1 as it stands, and --text-dim would be far brighter. */
.app[data-theme="light"] .comp-head .detail-src{color:var(--text-dim);}
/* On a phone the kicker line wraps anyway, so "updated" always takes a line of its
   own there and drops its "·", which would otherwise start that line. */
@media (max-width:560px){
  .comp-head .comp-src-upd{flex-basis:100%;}
  .comp-head .comp-src-dot{display:none;}
}
/* Raw --accent on --accent-soft is about 1.8:1 in dark. --focus is --accent in
   light, so light is unchanged. Scoped: the ledger and the wizard use .kindtag-auto
   too. */
.comp-head .kindtag-auto{color:var(--focus);}
/* Same reason: raw --accent is about 1.9:1 on the dark canvas. */
.comp-detail .back:hover,.comp-detail .back:focus-visible{color:var(--focus);}
@media (max-width:560px){
  .comp-head .detail-id{font-size:24px;}
  /* A bigger tap target for the only way back, with the negative margin paying for
     the extra padding so nothing below it moves. */
  .comp-detail .back{padding:8px 0;margin:-4px 0;}
}

/* Risk strip, under the header on both pages. The .dsec fill with no shadow or ring,
   not .plat-strip (shell.css gives that a console-only light shadow and ring). The
   -4px ties it to the header the way .admin-strip does. The cells are .plat-stat and
   the bar and legend are the list card's, reused as they are. */
.comp-strip{display:flex;align-items:center;background:var(--surface);border-radius:var(--radius);padding:14px 20px;margin-top:-4px;}
.comp-strip .plat-stat{flex:none;}
/* The same type as the list card's hero (.compcard .entcard-stat-hero b). */
.comp-strip .comp-stat-hero b{font-size:26px;letter-spacing:-.02em;}
.comp-strip-sev{flex:1 1 0;min-width:0;display:flex;flex-direction:column;gap:8px;padding-left:22px;border-left:1px solid var(--line);}
.comp-strip .entcard-legend{gap:6px 18px;}
.comp-strip .entcard-leg{font-size:12px;white-space:nowrap;}
.comp-strip .entcard-leg b{font:700 12px/1 var(--font-display);font-variant-numeric:tabular-nums;color:var(--text);}
/* A zero band is still rendered ("0 Low" is information) but steps back: fainter
   number and label, and a neutral dot. --text-faint, not --text-dim: in dark
   --text-dim (#e8e8fc) is next to --text (#fff), so the step was invisible there.
   (0,4,0) beats browse.css's (0,3,0) dot colours. */
.comp-strip .entcard-leg.is-zero,.comp-strip .entcard-leg.is-zero b{color:var(--text-faint);}
.comp-strip .entcard-leg.is-zero b{font-weight:500;}
.comp-strip .entcard-leg.is-zero .chip-dot{background:var(--line-2);}
/* Capped: the split is a top-MATCH_CAP slice, so its counts are not headline totals.
   --text-faint for the same both-themes reason. */
.comp-strip.is-capped .entcard-leg b{color:var(--text-faint);}
/* Text in --text-dim, not --ok: --ok as text is 3.3:1 on the light card. The check is
   a graphic, where 3:1 is the bar, so it keeps the green (drawn heavier: see the
   stroke rule in the inventory block). */
.comp-strip-clean{display:flex;align-items:center;gap:7px;font:600 12.5px/1.3 var(--font);color:var(--text-dim);}
.comp-strip-clean .ic{flex:none;color:var(--ok);}
/* Below 1280 the page is too narrow for stats and bar side by side: the stats keep
   row 1 and the bar and legend take a full row 2. */
@media (max-width:1280px){
  .comp-strip{flex-wrap:wrap;row-gap:12px;}
  .comp-strip .plat-stat{flex:0 1 auto;min-width:0;}
  .comp-strip-sev{flex-basis:100%;padding:12px 0 0;border-left:none;border-top:1px solid var(--line);}
}
/* Phones: equal cells (three on a component, two on a library), number above label,
   so a long label wraps inside its own cell instead of pushing the row off the card. */
@media (max-width:760px){
  .comp-strip{padding:14px 16px;}
  .comp-strip .plat-stat{flex:1 1 0;flex-direction:column;align-items:flex-start;gap:5px;padding:0 12px;}
  .comp-strip .plat-stat:first-child{padding-left:0;}
  .comp-strip .plat-stat span{font-size:11.5px;}
}

/* Side rail. .side-title's font shorthand already overrides the h3's UA size and
   weight; only the margin is left to reset. */
.comp-detail h3.side-title{margin:0;}
/* Attribution / Inventory entry: label left, value right, hairline between rows. */
.comp-attr-list{display:flex;flex-direction:column;margin:0;}
.comp-attr-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:9px 0;border-top:1px solid var(--line);}
.comp-attr-row:first-child{border-top:0;padding-top:0;}
.comp-attr-row:last-child{padding-bottom:0;}
.comp-attr-row dt{flex:none;font:13px/1.3 var(--font);color:var(--text-dim);}
.comp-attr-row dd{margin:0;min-width:0;text-align:right;font:600 13px/1.3 var(--font);color:var(--text);overflow-wrap:anywhere;}
/* The dd font shorthand above would otherwise beat .mono. */
.comp-attr-row dd.mono{font-family:var(--font-mono);font-size:12.5px;}
/* "Not specified" is an honest empty, not a value: regular weight, faint. */
.comp-attr-row dd.is-empty{font-weight:400;color:var(--text-faint);}
.comp-attr-oem{align-items:flex-start;}
.comp-attr-oem .reglist{justify-content:flex-end;}
/* A Jira chip beside an evidence file name used to cut the name to about 105px. Here
   the chip drops to a second line only when it has to. The evidence markup is shared
   with the vulnerability page and stays untouched. */
.comp-detail .detail-side .evi-name{flex-wrap:wrap;row-gap:4px;}

/* ===================================================== responsive */
@media (max-width:1080px){
  .wiz-body-rail{grid-template-columns:minmax(0,1fr);}
  .wiz-rail{position:static;flex-direction:row;overflow-x:auto;gap:6px;padding-bottom:4px;}
  .wiz-rail .wizstep{flex:none;}
  .wiz-rail .wizstep-sub{display:none;}
  .wiz-body-stepper{grid-template-columns:minmax(0,1fr);}
  .wiz-summary{position:static;}
  .rev-cols{grid-template-columns:1fr;}
}
@media (max-width:860px){
  .rev-row{grid-template-columns:84px 42px 114px 1fr 14px;}
  .rev-desc{display:none;}
  .catgrid{grid-template-columns:repeat(2,1fr);}
  .sbomsum{grid-template-columns:repeat(2,1fr);}
  .wiz-stepper{grid-auto-flow:row;grid-auto-columns:auto;}
}
@media (max-width:560px){
  .rev-row{grid-template-columns:78px 40px 1fr 14px;gap:9px;}
  .rev-lib{display:none;}
  /* Match the app shell's phone padding for the modal + review surfaces. */
  .usm-head,.usm-tabs,.usm-body,.usm-impact,.usm-foot{padding-left:16px;padding-right:16px;}
  .usm-scrim{padding:10px;}
  /* The SBOM summary stays two-up on the narrowest screens; the supplier picker
     (.suppgrid) sets its own column count. */
  .sbomsum{grid-template-columns:1fr 1fr;}
}

/* ===================================================== update-SBOM modal */
.usm-scrim{position:fixed;inset:0;z-index:300;background:color-mix(in oklab,#000 55%,transparent);backdrop-filter:blur(3px);
  display:flex;align-items:flex-start;justify-content:center;padding:48px 20px;overflow:auto;animation:fade .2s ease;}
.usm{width:100%;max-width:780px;background:var(--bg-panel);border:1px solid var(--line-2);border-radius:var(--radius);box-shadow:var(--shadow);display:flex;flex-direction:column;overflow:hidden;animation:rise .25s ease;}
.usm-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:20px 22px;border-bottom:1px solid var(--line);}
.usm-head h3{font:700 18px/1.2 var(--font-display);color:var(--text);margin:5px 0 0;}
.usm-tabs{display:flex;gap:4px;padding:14px 22px 0;border-bottom:1px solid var(--line);}
.usm-tabs .seg-btn{border-radius:8px 8px 0 0;padding:10px 14px;margin-bottom:-1px;border:1px solid transparent;border-bottom:none;}
.usm-tabs .seg-btn:hover{color:var(--text);background:var(--surface-2);}
.usm-tabs .seg-btn.is-on{background:var(--surface);color:var(--text);box-shadow:none;border-color:var(--line);border-bottom:1px solid var(--surface);}
.usm-body{padding:20px 22px;min-height:230px;max-height:50vh;overflow:auto;}
.usm-impact{display:flex;flex-direction:column;gap:10px;padding:14px 22px;background:var(--surface-2);border-top:1px solid var(--line);}
.usm-impact-stats{display:flex;align-items:center;gap:14px;flex-wrap:wrap;}
.usm-stat{font:13px/1.2 var(--font);color:var(--text-dim);display:flex;align-items:baseline;gap:8px;}
.usm-stat b{font:700 18px/1 var(--font-display);color:var(--text);}
.usm-stat b.is-good{color:var(--ok);}
.usm-stat em{font-style:normal;font-size:11.5px;color:var(--text-faint);}
.usm-pill{display:inline-flex;align-items:center;gap:6px;font:600 12px/1 var(--font);padding:6px 10px;border-radius:20px;}
.usm-pill-good{color:var(--ok);background:color-mix(in oklab,var(--ok) 14%,transparent);border:1px solid color-mix(in oklab,var(--ok) 30%,transparent);}
.usm-pill-warn{color:var(--sev-high);background:color-mix(in oklab,var(--sev-high) 14%,transparent);border:1px solid color-mix(in oklab,var(--sev-high) 30%,transparent);}
/* CVEs that fell out because their library left the inventory. Neutral, not green:
   nothing was fixed, so it must not read like the "to remediate" pill next to it. */
.usm-pill-mute{color:var(--text-dim);background:var(--surface);border:1px solid var(--line);}
.usm-remnote{font:12px/1.5 var(--font);color:var(--text-faint);}
/* Re-match failure inside the Update SBOM modal. Deliberately loud: while it is
   showing, the counts beside it are not a result and Apply is disabled. */
.usm-matcherr{display:flex;align-items:center;gap:8px;margin-top:10px;padding:8px 10px;
  border:1px solid var(--sev-critical);border-radius:8px;
  font:12px/1.45 var(--font);color:var(--text);}
.usm-matcherr > svg{flex:none;color:var(--sev-critical);}
.usm-remlist{display:flex;flex-wrap:wrap;gap:6px;}
.usm-remchip.is-muted{color:var(--text-faint);border-style:dashed;}
.usm-remchip{display:inline-flex;align-items:center;gap:6px;font:600 11px/1 var(--font-mono);color:var(--text-dim);background:var(--surface);border:1px solid var(--line);padding:5px 8px;border-radius:6px;}
.usm-foot{display:flex;align-items:center;gap:10px;padding:16px 22px;border-top:1px solid var(--line);}
.usm-foot-note{font:12px/1 var(--font);color:var(--text-faint);}

/* detail: update button + remediated subsection */
.dsec-head-aside{margin-left:auto;display:flex;align-items:center;gap:10px;}
.dsec-head-aside .dsec-kick{margin-left:0;}
.dsec-ic-ok{background:color-mix(in oklab,var(--ok) 16%,transparent);color:var(--ok);}
.ent-row-rem .ent-row-id{color:var(--text-dim);}
/* Not --ok: #16a34a as 12px text is 3.30:1 on the light card. The text keeps the
   base .ent-row-comp --text-faint, the same tier as the matched list's "via" in this
   column in both themes, and the green moves to the check glyph (a graphic). */
.ent-row-rem .ent-row-comp .ic{color:var(--ok);vertical-align:-2px;margin-right:5px;}

/* Component detail: SBOM inventory. One grid for the column header and every row,
   so type chips, suppliers and CVE badges each start at one x; as flex rows, every
   name pushed its chips somewhere different. Everything is under .compinv: the
   .librow / .liblist base rules above are the SBOM wizard preview's too. */
.compinv{--inv-cols:minmax(0,1fr) 160px 132px 64px 16px;}
/* The Supplier column only when some entry names one (anySupplier). Otherwise it is
   160px of blanks on every row. */
.compinv.compinv-nosup{--inv-cols:minmax(0,1fr) 132px 64px 16px;}
.compinv .compinv-head,.compinv .librow{display:grid;grid-template-columns:var(--inv-cols);align-items:center;column-gap:14px;}
.compinv .compinv-head{padding:10px 14px 9px;font:600 10.5px/1 var(--font);text-transform:uppercase;letter-spacing:.06em;color:var(--text-faint);}
.compinv .librow{padding:10px 14px;box-shadow:inset 3px 0 0 transparent;transition:background .12s,box-shadow .12s;}
/* Supplier + type share a wrapper only so the narrow layout below can put them on a
   second line; at full width it dissolves and they are ordinary grid cells. */
.compinv .compinv-meta{display:contents;}
/* Name and version are one run of text clamped to two lines, breaking at the <wbr>
   after each "_" first and anywhere only as a last resort. A one-line ellipsis cut
   the TAIL of a long AUTOSAR name, the part that tells dozens of modules sharing an
   "AUTOSAR_Classic_Platform_BSW_" prefix apart, while the version kept its width. */
.compinv .librow-name{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;min-width:0;white-space:normal;overflow-wrap:anywhere;line-height:1.3;}
.compinv .librow:not(.is-flagged) .librow-name{font-weight:500;}
.compinv .librow-sup{width:auto;}
.compinv .librow-type{justify-self:start;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:none;border:1px solid var(--line-2);padding:3px 6px;}
/* A flagged row names its WORST band: a 3px rail in that band's colour (--sc, set by
   the .sev-* class on the row) in place of the blanket high-orange wash. The count in
   the badge stays --text; severity lives only on the icon, the fill and the rail, and
   no severity colour is mixed toward text. */
.compinv .librow.is-flagged{background:none;box-shadow:inset 3px 0 0 var(--sc,var(--sev-high));}
.compinv .librow-cve{justify-self:start;color:var(--text);background:color-mix(in oklab,var(--sc,var(--sev-high)) 14%,transparent);border-color:color-mix(in oklab,var(--sc,var(--sev-high)) 34%,transparent);font-variant-numeric:tabular-nums;}
.compinv .librow-cve .ic{color:var(--sc,var(--sev-high));}
/* Critical is a solid badge. #b91c1c is the known 2.7:1 dark-surface red (the token
   stays), so as a tint and an icon it read WEAKER than a High row's orange: the worst
   inventories were the least visible. Solid, with white on it (6.47:1), it outranks
   every tinted badge in both themes and is still plainly red. */
.compinv .librow.sev-critical .librow-cve{color:#fff;background:var(--sc);border-color:var(--sc);}
.compinv .librow.sev-critical .librow-cve .ic{color:#fff;}
/* Full --ok, not the base .7 opacity (nominal token contrast 5.3:1 dark, 3.3:1 light,
   up from 3.2 and 2.3). */
.compinv .librow-ok{justify-self:start;width:auto;opacity:1;}
/* Icon draws a 1.6 stroke on a 24 viewBox: at 12-14px that is under a pixel, so it
   anti-aliases and never reaches full --ok, and the light ticks rendered at 2.4-2.9:1.
   A heavier stroke on this page's clean and fixed checks; no palette change. */
.compinv .librow-ok .ic,.comp-strip-clean .ic,.ent-row-rem .ent-row-comp .ic{stroke-width:2.4;}
/* Hover is the --surface-2 fill and a brighter chevron only. The severity rail stays:
   a --focus rail on hover was the same violet as a resting Medium rail (identical in
   light), so a Medium row looked permanently hovered. Keyboard focus adds the ring. */
.compinv button.librow:hover,.compinv button.librow:focus-visible{background:var(--surface-2);}
.compinv button.librow:focus-visible{outline:2px solid var(--focus);outline-offset:-2px;}
.compinv button.librow:hover .librow-chev,.compinv button.librow:focus-visible .librow-chev{color:var(--text);}
/* A container query, not a viewport @media: beside the rail at a 1250 viewport this
   section is about 559px wide, as cramped as a phone. Below 600 each row is two lines
   (name and version, then type), the badge or check on the right. The 560px
   .librow-sup{display:none} in responsive.css still applies on phones. */
.compinv-sec{container:compinv/inline-size;scroll-margin-top:calc(var(--topbar-h) + 12px);}
@container compinv (max-width:599px){
  .compinv .compinv-head{display:none;}
  .compinv .librow{grid-template-columns:minmax(0,1fr) auto 16px;grid-template-areas:"name hit chev" "meta hit chev";row-gap:6px;}
  .compinv .librow-name{grid-area:name;}
  .compinv .compinv-meta{grid-area:meta;display:flex;align-items:center;gap:8px;min-width:0;}
  .compinv .librow-type{order:-1;flex:none;max-width:55%;}
  .compinv .librow-sup{flex:0 1 auto;min-width:0;}
  .compinv .librow-cve,.compinv .librow-ok{grid-area:hit;}
  .compinv .librow-chev{grid-area:chev;}
}
/* The head folds like the matched list's: the kicker stays beside the title and the
   button takes a full-width second line on a narrow card. The kicker's own auto margin
   does the pushing, so the aside gives its up (two would split the slack). */
.compinv-sec .compinv-actions{margin-left:0;}
@container compinv (max-width:599px){
  .compinv-sec .compinv-actions{flex-basis:100%;}
  .compinv-sec .compinv-actions>.btn{flex:1 1 0;justify-content:center;}
}
/* The name filter, shown only once the list pages. .search is the ledger toolbar's
   box (flex:1, min-width:240); here it is a fixed 320px that may shrink on a phone. */
.compinv-bar{margin-bottom:10px;}
.compinv-search{flex:none;width:min(320px,100%);min-width:0;height:34px;}
/* The input is only 20px tall in its 34px box, and a tap on the box's padding did not
   focus it: stretch it to the full height. */
.compinv-search input{align-self:stretch;min-width:0;}
/* ledger.css rings .search-clear only inside .toolbar/.filterbar. */
.compinv-search .search-clear:focus-visible{outline:2px solid var(--focus);outline-offset:1px;}
.compinv-none{padding:18px 14px;border:1px solid var(--line);border-radius:var(--radius-sm);font:13px/1.4 var(--font);color:var(--text-faint);}

/* ===================================================== ledger column-header filters */
.lc-head-cell{position:relative;padding-right:22px;}
.lc-head-cell.is-filtered .lc-head-lab{color:var(--accent);}
.colfilt{position:absolute;right:0;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;gap:2px;
  border:none;background:none;color:var(--text-faint);cursor:pointer;padding:3px;border-radius:5px;line-height:0;}
.colfilt:hover{color:var(--text-dim);background:var(--surface-3);}
.colfilt.is-open{color:var(--text);background:var(--surface-3);}
.colfilt.is-active{color:var(--accent);}
.colfilt-badge{font:700 9px/1 var(--font-mono);color:var(--accent-ink);background:var(--accent);border-radius:10px;padding:2px 4px;}
.app[data-theme="light"] .colfilt-badge{color:#fff;}

/* ===================================================== component detail: matched-vulnerability list
   The component and library pages' matched lists, and the Remediated list, which
   uses the same row contract. Every row used to be its own grid with an `auto`
   status track, so each pill's width moved the "via lib" column and nothing lined
   up down the list. The tracks are fixed now, and the layout keys off the SECTION's
   width with @container rather than the viewport. A container query sees the
   content box: ~750px at a 1440 viewport, ~840 at 1180 (the rail drops below it)
   and ~510 at 1200, so a viewport @media picks the wrong layout at every one of
   those. (The move from b226b3b.) .content caps at 1320px, so the section never
   passes ~878 (~894 compact): the two-line row IS the widest layout. */
.mvl-sec{container:mvl/inline-size;--mvl-pad:20px;scroll-margin-top:calc(var(--topbar-h) + 12px);}
/* MUST equal the .dsec padding (detail.css 20px, responsive.css 16px at 560 and below):
   the list bleeds out to the card edge by exactly this much and pads its rows back in. */
@media (max-width:560px){.mvl-sec{--mvl-pad:16px;}}
.mvl-list{margin-inline:calc(-1*var(--mvl-pad));border-block:1px solid var(--line);}
/* Two lines by default: id + via over the description, severity / status / chevron
   centred across both. The description gets ~400px instead of the ~105 it was left
   once five single-line columns had taken theirs. */
.mvl-list>.mvl-row{padding-inline:var(--mvl-pad);grid-template-columns:104px 132px minmax(0,1fr) 120px 15px;grid-template-areas:"sev id via stat chev" "sev desc desc stat chev";row-gap:4px;box-shadow:inset 3px 0 0 transparent;transition:background .1s,box-shadow .12s;}
.mvl-row>.sevtag{grid-area:sev;}
/* The tag stretches to its track; a growing label pins the CVSS numeral to the
   right edge, so the scores read as a column too. */
.mvl-row .sevtag-label{flex:1 1 auto;}
/* The id track is a fixed 132px; a GHSA id (19 mono characters) would spill into the
   next column, so it ellipsises here. The button text still carries it in full. */
.mvl-row>.ent-row-id{grid-area:id;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.mvl-row>.ent-row-desc{grid-area:desc;min-width:0;}
.mvl-row>.mvl-via{grid-area:via;display:block;min-width:0;}
.mvl-row>.mvl-stat{grid-area:stat;display:flex;min-width:0;}
/* Every pill the same width at the same x ("Accepted risk" fits 120). A longer
   admin-defined status label clips; the wrapper's title carries it in full. */
.mvl-stat>.statuspill{flex:1 1 auto;min-width:0;overflow:hidden;justify-content:center;}
.mvl-row>.ent-row-chev{grid-area:chev;}
.mvl-list.mvl-novia>.mvl-row{grid-template-columns:104px minmax(0,1fr) 120px 15px;grid-template-areas:"sev id stat chev" "sev desc stat chev";}
/* Narrow section (a phone, or beside the rail at ~1180-1255): a three-line card.
   Severity, id and status on top, then the description, then "via" — kept, not
   dropped as the old 760/560px rules did, because which library matched is the one
   fact this page adds to the CVE. The status track stays a fixed 120px, so the pills
   keep one width at one x here too. The chevron goes: the whole card is the tap target. */
@container mvl (max-width:559px){
  .mvl-list>.mvl-row{grid-template-columns:100px minmax(0,1fr) 120px;grid-template-areas:"sev id stat" "desc desc desc" "via via via";column-gap:8px;row-gap:6px;}
  .mvl-list.mvl-novia>.mvl-row{grid-template-columns:100px minmax(0,1fr) 120px;grid-template-areas:"sev id stat" "desc desc desc";}
  .mvl-row>.ent-row-chev{display:none;}
}
/* A 360-390px phone: beside a 120px pill the id got under 100px and read
   "CVE-2022-…", so rows sharing a prefix looked alike. The id takes the whole first
   line and the pill moves to the last, beside "via" (or alone, right, on a library). */
@container mvl (max-width:349px){
  .mvl-list>.mvl-row{grid-template-areas:"sev id id" "desc desc desc" "via via stat";}
  .mvl-list.mvl-novia>.mvl-row{grid-template-areas:"sev id id" "desc desc desc" ". . stat";}
}
/* Hover rail and keyboard ring: the house rail from admin.css, on --focus. Raw
   --accent is 1.5-1.8:1 on the dark card; --focus is 5.65:1 there and 8.97:1 on
   the light hover fill. The --surface-2 hover fill from browse.css stays. */
.mvl-list>.mvl-row:hover{box-shadow:inset 3px 0 0 var(--focus);}
.mvl-list>.mvl-row:focus-visible{outline:2px solid var(--focus);outline-offset:-2px;box-shadow:inset 3px 0 0 var(--focus);}

/* Section head. The kicker sits outside the actions now; with two auto margins the
   slack would split and float the kicker mid-line, so the actions give theirs up. */
.mvl-sec .mvl-actions{margin-left:0;}
/* A narrow card folds the actions onto a full-width second line of equal buttons
   (this relies on the page-wide .comp-detail .dsec-head wrap). */
@container mvl (max-width:599px){
  .mvl-sec .mvl-actions{flex-basis:100%;}
  .mvl-sec .mvl-actions>.btn{flex:1 1 0;justify-content:center;}
}
/* b226b3b's .admin-card .btn-accent move: the #3a1d73 fill is 1.34:1 against the dark
   card, so the button had no visible edge. The --focus rim is 5.65:1 there; in light it
   is a faint #49299a edge on the #3a1d73 fill, as on Administration. */
.mvl-sec .dsec-head .btn-accent{border-color:var(--focus);}

/* Triage-status chips over the matched list: the Administration filter chips, a
   size down to sit inside a section rather than above a page (32px tall on phones:
   see the frame block). */
.mvl-filters{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 12px;}
.mvl-filters .admin-filter{height:28px;padding:0 11px;}
.mvl-filters .admin-filter:focus-visible{outline:2px solid var(--focus);outline-offset:2px;}

/* Pagers on this page (matched, remediated, inventory). A 150-match component's strip
   [<][1]…[4][5][6]…[10][>] is wider than a phone card, so the controls wrap instead
   of pushing the page sideways, and on phones Prev/Next shrink to 34px arrow squares.
   font-size:0 hides the words from sight only; they stay the buttons' accessible name. */
.comp-detail .pager-ctrls{flex-wrap:wrap;justify-content:flex-end;row-gap:6px;}
.comp-detail .pager-btn:focus-visible,.comp-detail .pager-num:focus-visible{outline:2px solid var(--focus);outline-offset:2px;}
@media (max-width:560px){
  .comp-detail .pager-info{flex-basis:100%;}
  .comp-detail .pager-ctrls{margin-left:0;}
  .comp-detail .pager-btn{font-size:0;gap:0;padding:0;}
}

/* ===================================================== SBOM from source code */
/* The SBOM step's two ways in: "Upload SBOM file" | "Generate from source code"
   (sbom/generate_sbom.jsx). The same generator panel sits in the update-SBOM dialog
   as its third tab. */
.sbom-src-seg{align-self:flex-start;}
.gen{display:flex;flex-direction:column;gap:14px;}
.drop.is-off{border-style:solid;}
.drop.drop-compact{padding:28px 20px;}
.gen-progress{display:block;width:min(320px,80%);height:4px;border-radius:2px;background:var(--surface-3);overflow:hidden;margin-top:4px;}
.gen-progress span{display:block;height:100%;background:var(--focus);transition:width .2s;}
.gen-note{display:flex;gap:8px;align-items:flex-start;margin:0;font:12px/1.55 var(--font);color:var(--text-dim);}
.gen-note .ic{flex:none;margin-top:2px;}
.gen-note .mono{font-size:11.5px;color:var(--text);}
/* A generated SBOM that may be incomplete. Amber, like .usm-pill-warn: the result is usable, it is
   just not the whole picture. Shown beside the inventory, because that is what gets relied on. The
   words are in the text colour for the reason .gen-err's are. */
.gen-warn{display:flex;gap:8px;align-items:flex-start;margin:0 0 12px;padding:10px 12px;border-radius:var(--radius-sm);
  font:13px/1.45 var(--font);color:var(--text);background:color-mix(in oklab,var(--sev-high) 12%,transparent);}
.gen-warn > svg{flex:none;margin-top:2px;color:var(--sev-high);}
.gen-warn-head{display:block;font-weight:600;margin-bottom:2px;}
.gen-warn ul{margin:0;padding:0;list-style:none;}
.gen-warn li + li{margin-top:2px;}
.usm-genbar + .gen-warn{margin-top:10px;}
/* "Try again" inside an error box, kept to the right of the text. */
.gen-retry{flex:none;margin-left:auto;}
.wizform-head-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;}
/* Shown above the inventory editor after a generated SBOM replaced it. */
.usm-genbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 12px;padding:9px 12px;
  border:1px solid var(--line);border-radius:8px;background:var(--surface-2);font:12.5px/1.45 var(--font);color:var(--text-dim);}
.usm-genbar>.ic{flex:none;color:var(--ok);}
.usm-genbar>span{flex:1;min-width:200px;}
.usm-genbar .mono{color:var(--text);}
@media (max-width:560px){
  .sbom-src-seg{align-self:stretch;}
}
/* Identity step: what the ECU itself is. Optional, so folded until wanted. */
.ecu-details{border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface);padding:0 14px;}
.ecu-details>summary{display:flex;align-items:center;gap:2px;min-height:44px;cursor:pointer;list-style:none;
  font:600 13px/1.2 var(--font);color:var(--text);}
.ecu-details>summary::-webkit-details-marker{display:none;}
.ecu-details>summary::before{content:"";width:7px;height:7px;margin:0 10px 0 2px;border:solid var(--text-dim);
  border-width:0 1.5px 1.5px 0;transform:rotate(-45deg);transition:transform .15s;flex:none;}
.ecu-details[open]>summary::before{transform:rotate(45deg);}
.ecu-details>summary:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:4px;}
.ecu-details-sub{margin:0 0 12px;font:12.5px/1.5 var(--font);color:var(--text-dim);}
.ecu-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(220px,100%),1fr));gap:14px;padding-bottom:16px;}

/* Below the SBOM drop zone: the CSV template for those who have no SBOM. */
.sbom-template{display:flex;align-items:center;gap:8px 10px;flex-wrap:wrap;margin:12px 0 0;
  font:12.5px/1.5 var(--font);color:var(--text-dim);}
.sbom-template>.ic{flex:none;}
.sbom-template>span{flex:1;min-width:min(260px,100%);}

/* Component page, Attribution card: the build's SBOM, original or in another format. */
.comp-sbom-dl{margin-top:14px;}

/* "Download SBOM ▾": one button, the formats in a menu beneath it. */
.dlmenu{position:relative;display:inline-flex;flex-direction:column;align-items:flex-end;}
.dlmenu-block{display:flex;align-items:stretch;}
.dlmenu .dlmenu-caret{margin-left:2px;opacity:.75;}
.dlmenu-list{position:absolute;top:calc(100% + 6px);right:0;z-index:40;display:flex;flex-direction:column;gap:2px;
  width:max-content;min-width:240px;max-width:min(320px,calc(100vw - 32px));padding:6px;
  border:1px solid var(--line-2);border-radius:var(--radius-sm);background:var(--surface);box-shadow:var(--shadow);}
.dlmenu-block .dlmenu-list{left:0;right:0;width:auto;max-width:none;}
.dlmenu-item{display:flex;flex-direction:column;align-items:flex-start;gap:3px;padding:8px 10px;border:0;border-radius:6px;
  background:none;color:var(--text);text-align:left;cursor:pointer;font:600 13px/1.25 var(--font);}
.dlmenu-item:hover{background:var(--surface-2);}
.dlmenu-item:focus-visible{background:var(--surface-2);outline:2px solid var(--focus);outline-offset:-2px;}
.dlmenu-hint{font:400 11.5px/1.35 var(--font);color:var(--text-dim);}
.dlmenu-err{margin-top:8px;max-width:min(320px,calc(100vw - 32px));color:var(--text);}
.dlmenu-err > svg{flex:none;color:var(--sev-critical);}
