/* ==========================================================================
   THUGS(red) Security Check — components.css
   --------------------------------------------------------------------------
   Every component class the templates are written against, plus the few
   extras noted in html/css/README.md. Requires base.css for the tokens.

   Two rules hold everywhere in this file:

   1. NO INLINE STYLES. The site ships a strict CSP with no 'unsafe-inline'
      for style-src, so nothing here may depend on a style="" attribute.
      Where a component needs a runtime value (a meter percentage, a bar
      length) it is driven by a *data attribute* selector or by SVG geometry
      attributes — see .meter and charts.css.

   2. STATUS IS NEVER COLOUR ALONE. Every .badge / .alert / .grade carries a
      bracketed sigil and a text label, because "detected" vs "not detected"
      is the whole product and must survive greyscale, CVD and forced-colors.
   ========================================================================== */

/* ============================================================== 1. CARD === */

.card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--surface) 96%, transparent),
    color-mix(in srgb, var(--bg-alt) 96%, transparent));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .card { background: var(--surface); }
}

/* .card-head doubles as the terminal title bar: three dots, a ~/ path
   title, and a right-aligned action slot. */
.card-head {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  min-width: 0;
  padding: .5rem .8rem;
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  font-size: .8rem;
  color: var(--fg-dim);
}

.card-head h2,
.card-head h3,
.card-head h4 {
  margin: 0;
  font-family: var(--mono);
  font-size: .84rem;
  font-weight: 700;
  letter-spacing: .03em;
  color: var(--fg-bright);
  min-width: 0;
}

.card-head > .push,
.card-head-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
}

/* Decorative window dots. <span class="card-dots" aria-hidden="true"></span> */
.card-dots {
  display: inline-flex;
  gap: .3rem;
  flex: none;
}

.card-dots::before,
.card-dots::after,
.card-dots > i {
  content: "";
  display: block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--bad);
}

.card-dots::after { background: var(--warn); }
.card-dots > i { background: var(--ok); }

/* "~/reports/upload" style path title. */
.card-path::before {
  content: "~/";
  color: var(--fg-faint);
}

.card-body {
  padding: clamp(.9rem, 2.2vw, 1.4rem);
  flex: 1 1 auto;
  min-width: 0;
}

.card-body > :last-child { margin-bottom: 0; }

.card-body-flush { padding: 0; }

.card-foot {
  margin-top: auto;
  padding: .6rem clamp(.9rem, 2.2vw, 1.4rem);
  border-top: 1px solid var(--line-soft);
  background: color-mix(in srgb, var(--surface-2) 60%, transparent);
  color: var(--fg-faint);
  font-size: .78rem;
  display: flex;
  gap: .5rem;
  align-items: center;
  flex-wrap: wrap;
}

@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .card-foot { background: var(--surface-2); }
}

/* An uppercase kicker above a card title. */
.card-sigil {
  display: block;
  margin-bottom: .3rem;
  color: var(--accent);
  font-size: .72rem;
  letter-spacing: .13em;
  text-transform: uppercase;
}

/* A whole card that is a link. */
a.card,
.card-link {
  border-bottom: 1px solid var(--line);
  color: inherit;
  transition: border-color var(--t) ease, transform var(--t) ease,
              box-shadow var(--t) ease;
}

a.card:hover,
a.card:focus-visible,
.card-link:hover,
.card-link:focus-visible {
  border-color: var(--accent-line);
  transform: translateY(-2px);
  box-shadow: var(--glow-accent);
  color: inherit;
}

/* Accent edge variants for at-a-glance card severity. */
.card-ok   { border-left: 3px solid var(--ok); }
.card-warn { border-left: 3px solid var(--warn); }
.card-bad  { border-left: 3px solid var(--bad); }
.card-info { border-left: 3px solid var(--info); }
.card-accent { border-left: 3px solid var(--accent); }

/* ---- .card-grid: auto-fit responsive ----------------------------------- */

.card-grid {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(var(--col, 270px), 1fr));
  align-items: stretch;
}

.card-grid > * { min-width: 0; }

.card-grid-sm { --col: 200px; }
.card-grid-lg { --col: 340px; }
.card-grid-2  { --col: 420px; }

/* A sidebar split that collapses to one column. */
.split {
  display: grid;
  gap: var(--gap);
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 900px) {
  .split { grid-template-columns: minmax(0, 1fr) minmax(220px, 300px); }
  .split-left { grid-template-columns: minmax(220px, 300px) minmax(0, 1fr); }
}


/* =========================================================== 2. BUTTONS === */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  padding: .45rem .9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--fg);
  font-family: var(--mono);
  font-size: .86rem;
  font-weight: 700;
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: filter var(--t-fast) ease, transform var(--t-fast) ease,
              background-color var(--t-fast) ease, border-color var(--t-fast) ease,
              color var(--t-fast) ease;
}

/* Text-based [bracketed] affordance, the way the main site marks a link. */
.btn::before { content: "["; color: currentColor; opacity: .5; }
.btn::after  { content: "]"; color: currentColor; opacity: .5; }

.btn-nobracket::before,
.btn-nobracket::after { content: none; }

.btn:hover,
.btn:focus-visible {
  color: var(--fg-bright);
  border-color: var(--accent-line);
  background: var(--surface-3);
  transform: translateY(-1px);
}

.btn:active { transform: translateY(0); }

.btn[disabled],
.btn[aria-disabled="true"],
.btn.is-disabled {
  opacity: .5;
  cursor: not-allowed;
  transform: none;
  pointer-events: none;
}

/* --- variants --- */

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

.btn-primary:hover,
.btn-primary:focus-visible {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  filter: brightness(1.12);
}

.btn-danger {
  background: transparent;
  border-color: var(--bad-line);
  color: var(--bad);
}

.btn-danger:hover,
.btn-danger:focus-visible {
  background: var(--bad);
  border-color: var(--bad);
  color: var(--accent-ink);
}

:root[data-theme="light"] .btn-danger:hover,
:root[data-theme="light"] .btn-danger:focus-visible { color: #fff; }

.btn-ok {
  background: transparent;
  border-color: var(--ok-line);
  color: var(--ok);
}

.btn-ok:hover,
.btn-ok:focus-visible {
  background: var(--ok-soft);
  border-color: var(--ok);
  color: var(--ok);
}

.btn-ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--fg-dim);
}

.btn-ghost:hover,
.btn-ghost:focus-visible {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--fg-bright);
}

.btn-sm {
  padding: .22rem .55rem;
  font-size: .76rem;
  gap: .3rem;
}

.btn-lg {
  padding: .65rem 1.3rem;
  font-size: .95rem;
}

.btn-block { display: flex; width: 100%; }

.btn-icon {
  padding: .3rem .45rem;
  min-width: 2rem;
}

.btn-icon::before,
.btn-icon::after { content: none; }

.btn svg {
  width: 1em;
  height: 1em;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: 1rem;
  align-items: center;
}

/* Theme toggle lives in the header; hidden entirely without JS because it
   would do nothing. ui.js removes the hidden attribute on boot. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .28rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--fg-dim);
  font-size: .76rem;
  cursor: pointer;
}

.theme-toggle:hover,
.theme-toggle:focus-visible {
  color: var(--fg-bright);
  border-color: var(--accent-line);
}

.theme-toggle svg { width: 1.05em; height: 1.05em; flex: none; }

/* Show the moon in dark mode, the sun in light mode. */
.theme-toggle .icon-light { display: none; }
:root[data-theme="light"] .theme-toggle .icon-light { display: inline; }
:root[data-theme="light"] .theme-toggle .icon-dark  { display: none; }


/* ============================================================= 3. TABLE === */

/* .table-wrap is what keeps a wide statistics table usable at 360px: it
   scrolls horizontally on its own, inside its card, instead of widening the
   page. tabindex="0" on the wrapper (set by ui.js) makes that scroll
   reachable from the keyboard. */
.table-wrap {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  -webkit-overflow-scrolling: touch;
}

.table-wrap:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

/* Fade hint at the right edge so the scroll is discoverable. */
.table-wrap.has-overflow {
  background-image: linear-gradient(to left,
    color-mix(in srgb, var(--accent) 12%, transparent), transparent 3rem);
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: .86rem;
  background: var(--surface);
}

.table caption {
  padding: .5rem .7rem;
  color: var(--fg-faint);
  font-size: .76rem;
  text-align: left;
}

.table th,
.table td {
  padding: .55rem .7rem;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
}

.table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-2);
  color: var(--fg-dim);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom: 1px solid var(--line);
}

.table tbody tr:last-child td { border-bottom: 0; }

.table tbody tr:hover { background: var(--accent-soft); }

.table tfoot td {
  background: var(--surface-2);
  color: var(--fg-bright);
  font-weight: 700;
  border-top: 1px solid var(--line);
}

.table-striped tbody tr:nth-child(even) { background: var(--bg-alt); }
.table-striped tbody tr:nth-child(even):hover { background: var(--accent-soft); }

.table-compact th,
.table-compact td { padding: .32rem .5rem; font-size: .8rem; }

/* Numeric columns right-align and use tabular figures so digits stack. */
.table .num,
.table td.num,
.table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.table .row-actions {
  display: flex;
  gap: .35rem;
  flex-wrap: wrap;
}

/* A grouping row inside the body. */
.table .row-group td {
  background: var(--surface-2);
  color: var(--accent);
  font-size: .74rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  border-bottom: 1px solid var(--line);
}

.table .row-group .count {
  color: var(--fg-faint);
  letter-spacing: 0;
  text-transform: none;
}

/* Highlight the viewer's own row in a comparative table. */
.table tr.is-you {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
}

.table tr.is-you:hover { background: var(--accent-soft); }

/* ---- sortable headers -------------------------------------------------- */

/* ui.js promotes each sortable th's text into a real <button> so sorting is
   keyboard operable and announced; with JS off the header is inert text and
   the table simply arrives in its server-side order.

   The th KEEPS its normal padding, so the no-JS header still has spacing —
   zeroing it here and relying on the button's padding made every header run
   into the next one whenever the script did not load. The button instead
   cancels that padding with a negative margin and re-adds it, which gives a
   full-bleed click target without the cell depending on the button existing. */
.table.is-sortable th > button,
.table.is-sortable th .th-sort {
  display: flex;
  align-items: center;
  gap: .35rem;
  width: calc(100% + 1.4rem);
  margin: -.55rem -.7rem;
  padding: .55rem .7rem;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: left;
  cursor: pointer;
}

.table.is-sortable th > button:hover,
.table.is-sortable th > button:focus-visible { color: var(--fg-bright); }

.table.is-sortable th.num > button { justify-content: flex-end; }

/* The indicator is generated content driven by aria-sort, so the DOM never
   carries a decorative glyph and the state has exactly one source. */
.table.is-sortable th > button::after {
  content: "\2195";           /* ↕ unsorted */
  color: var(--fg-faint);
  font-size: .9em;
  opacity: .6;
}

.table.is-sortable th[aria-sort="ascending"] > button::after {
  content: "\25B2";           /* ▲ */
  color: var(--accent);
  opacity: 1;
}

.table.is-sortable th[aria-sort="descending"] > button::after {
  content: "\25BC";           /* ▼ */
  color: var(--accent);
  opacity: 1;
}

.table.is-sortable th[aria-sort]:not([aria-sort="none"]) { background: var(--surface-3); }

/* ---- mobile: optional stacked layout ----------------------------------
   Add .table-stack and give each td a data-label; below 560px the table
   becomes a list of labelled rows. .table-wrap alone is enough for wide
   numeric tables — this is for narrow, text-heavy ones.               */

@media (max-width: 560px) {
  .table-stack thead { display: none; }

  .table-stack tbody tr {
    display: block;
    padding: .5rem .1rem;
    border-bottom: 1px solid var(--line);
  }

  .table-stack tbody td {
    display: flex;
    gap: .6rem;
    align-items: baseline;
    padding: .18rem .6rem;
    border: 0;
    text-align: left;
  }

  .table-stack tbody td::before {
    content: attr(data-label);
    flex: 0 0 8.5rem;
    color: var(--fg-faint);
    font-size: .72rem;
    letter-spacing: .06em;
    text-transform: uppercase;
  }

  .table-stack tbody td.num { justify-content: flex-start; }
}


/* ============================================================ 4. BADGES === */

/* <span class="badge badge-ok">detected</span>
   The ::before sigil is the non-colour channel. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .08rem .45rem;
  border: 1px solid var(--neutral-line);
  border-radius: var(--radius-xs);
  background: var(--neutral-soft);
  color: var(--fg-dim);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1.5;
  white-space: nowrap;
  vertical-align: baseline;
}

.badge::before {
  content: "[·]";
  font-weight: 400;
  opacity: .9;
}

.badge-ok {
  border-color: var(--ok-line);
  background: var(--ok-soft);
  color: var(--ok);
}
.badge-ok::before { content: "[+]"; }        /* detected — good news */

.badge-warn {
  border-color: var(--warn-line);
  background: var(--warn-soft);
  color: var(--warn);
}
.badge-warn::before { content: "[!]"; }      /* blocked / degraded */

.badge-bad {
  border-color: var(--bad-line);
  background: var(--bad-soft);
  color: var(--bad);
}
.badge-bad::before { content: "[\2013]"; }   /* [–] not detected — bad news */

.badge-info {
  border-color: var(--info-line);
  background: var(--info-soft);
  color: var(--info);
}
.badge-info::before { content: "[i]"; }

.badge-neutral {
  border-color: var(--neutral-line);
  background: var(--neutral-soft);
  color: var(--fg-dim);
}
.badge-neutral::before { content: "[?]"; }

.badge-accent {
  border-color: var(--accent-line);
  background: var(--accent-soft);
  color: var(--accent);
}
.badge-accent::before { content: "[*]"; }

/* Suppress the sigil where the badge sits in a column that already says
   what it is (e.g. a dense matrix cell). */
.badge-bare::before { content: none; }

.badge-lg { font-size: .82rem; padding: .18rem .6rem; }

/* .chip — a neutral, non-status tag (vendor names, platforms, categories). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .1rem .45rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--fg-dim);
  font-size: .74rem;
  white-space: nowrap;
}

a.chip { border-bottom: 1px solid var(--line); }
a.chip:hover,
a.chip:focus-visible { color: var(--fg-bright); border-color: var(--accent-line); }

.chip-row { display: flex; flex-wrap: wrap; gap: .3rem; }


/* ============================================================ 5. ALERTS === */

/* <div class="alert alert-ok" role="status">…</div>
   Errors should use role="alert". The sigil is generated, so the message
   text is never duplicated for a screen reader. */
.alert {
  display: flex;
  align-items: flex-start;
  gap: .6rem;
  padding: .7rem .9rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--fg-faint);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--fg);
  font-size: .88rem;
}

.alert::before {
  content: "[*]";
  flex: none;
  font-weight: 700;
  color: var(--fg-faint);
  line-height: 1.5;
}

.alert > :last-child { margin-bottom: 0; }

.alert p { margin: 0 0 .4rem; }

.alert-title {
  display: block;
  margin-bottom: .15rem;
  color: var(--fg-bright);
  font-weight: 700;
}

.alert-body { min-width: 0; flex: 1 1 auto; }

.alert-ok {
  border-left-color: var(--ok);
  background: var(--ok-soft);
}
.alert-ok::before { content: "[+]"; color: var(--ok); }

.alert-error {
  border-left-color: var(--bad);
  background: var(--bad-soft);
}
.alert-error::before { content: "[x]"; color: var(--bad); }

.alert-warn {
  border-left-color: var(--warn);
  background: var(--warn-soft);
}
.alert-warn::before { content: "[!]"; color: var(--warn); }

.alert-info {
  border-left-color: var(--info);
  background: var(--info-soft);
}
.alert-info::before { content: "[i]"; color: var(--info); }

/* Dismiss button — ui.js wires it; hidden when JS is off so nothing looks
   broken. Templates render it with the hidden attribute. */
.alert-dismiss {
  margin-left: auto;
  flex: none;
  padding: 0 .3rem;
  border: 0;
  background: none;
  color: var(--fg-faint);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.alert-dismiss:hover,
.alert-dismiss:focus-visible { color: var(--bad); }

/* Stack of flash messages, usually straight under the header. */
.flash-stack {
  display: grid;
  gap: .5rem;
  margin-bottom: var(--gap);
}

.flash-stack:empty { display: none; }

/* A quieter inline notice, for form-level context rather than an event. */
.notice {
  padding: .55rem .8rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-alt);
  color: var(--fg-dim);
  font-size: .82rem;
}

.notice-warn { border-color: var(--warn-line); color: var(--warn); }
.notice-bad  { border-color: var(--bad-line);  color: var(--bad); }


/* ============================================================== 6. FORMS === */

.form-row {
  display: grid;
  gap: .3rem;
  margin-bottom: 1rem;
  min-width: 0;
}

.form-row-2,
.form-row.two {
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
}

.form-row-3,
.form-row.three {
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 1rem;
}

.form-row-inline {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .6rem;
}

.form-grid {
  display: grid;
  gap: 1rem;
}

.form-narrow { max-width: 460px; }

.label {
  display: block;
  color: var(--fg-dim);
  font-size: .82rem;
  letter-spacing: .02em;
}

.label .req,
.label-req {
  color: var(--accent);
  margin-left: .15rem;
}

.label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .6rem;
}

/* Shared field skin. Applied via the explicit classes the templates use,
   and also to bare inputs so an un-classed field never looks unstyled. */
.input,
.select,
.textarea,
input[type="text"],
input[type="email"],
input[type="password"],
input[type="url"],
input[type="number"],
input[type="search"],
input[type="date"],
input[type="datetime-local"],
input[type="tel"],
input[type="file"],
select,
textarea {
  width: 100%;
  padding: .5rem .65rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--bg-alt);
  color: var(--fg-bright);
  font-family: var(--mono);
  font-size: .9rem;
  line-height: 1.5;
  transition: border-color var(--t-fast) ease, box-shadow var(--t-fast) ease;
}

.input:hover,
.select:hover,
.textarea:hover { border-color: var(--fg-faint); }

.input:focus,
.select:focus,
.textarea:focus,
input:focus,
select:focus,
textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Keep a visible ring for keyboard users even though :focus is styled. */
.input:focus-visible,
.select:focus-visible,
.textarea:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.input::placeholder,
.textarea::placeholder,
input::placeholder,
textarea::placeholder { color: var(--fg-faint); opacity: 1; }

.input[disabled],
.select[disabled],
.textarea[disabled],
.input[readonly] {
  opacity: .6;
  cursor: not-allowed;
}

.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"],
.is-invalid {
  border-color: var(--bad);
  box-shadow: 0 0 0 3px var(--bad-soft);
}

.textarea,
textarea {
  min-height: 7rem;
  resize: vertical;
  line-height: 1.6;
}

.textarea-code {
  min-height: 14rem;
  background: var(--bg);
  font-size: .84rem;
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

/* Native select arrow replaced with a CSS-drawn caret (no image request). */
.select,
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 2rem;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--fg-dim) 50%),
    linear-gradient(135deg, var(--fg-dim) 50%, transparent 50%);
  background-position: calc(100% - 15px) center, calc(100% - 10px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.select[multiple],
select[multiple] {
  background-image: none;
  padding-right: .65rem;
  min-height: 7rem;
}

input[type="file"] { padding: .35rem; cursor: pointer; }

input[type="file"]::file-selector-button {
  margin-right: .6rem;
  padding: .3rem .6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--fg);
  font-family: var(--mono);
  font-size: .82rem;
  cursor: pointer;
}

input[type="file"]::file-selector-button:hover {
  border-color: var(--accent-line);
  color: var(--fg-bright);
}

/* ---- .checkbox: wraps an input + label as one hit target --------------- */

.checkbox {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  color: var(--fg);
  font-size: .86rem;
  cursor: pointer;
}

.checkbox input[type="checkbox"],
.checkbox input[type="radio"] {
  width: 1.05em;
  height: 1.05em;
  margin-top: .25em;
  flex: none;
  accent-color: var(--accent);
  cursor: pointer;
}

.checkbox-text { min-width: 0; }

.checkbox-hint {
  display: block;
  color: var(--fg-faint);
  font-size: .76rem;
}

.checkbox-group {
  display: grid;
  gap: .4rem;
}

.form-hint {
  color: var(--fg-faint);
  font-size: .76rem;
}

.form-error {
  display: flex;
  align-items: baseline;
  gap: .3rem;
  color: var(--bad);
  font-size: .78rem;
  font-weight: 700;
}

.form-error::before { content: "[x]"; font-weight: 400; }

.form-error:empty { display: none; }

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .6rem;
  align-items: center;
  margin-top: 1.2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line-soft);
}

/* A toolbar of filters that scopes everything below it — one row, above the
   charts, never per-chart (see charts.css). */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: .6rem 1rem;
  padding: .7rem .9rem;
  margin-bottom: var(--gap);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.toolbar .form-row { margin-bottom: 0; }
.toolbar .label { font-size: .72rem; letter-spacing: .06em; text-transform: uppercase; }


/* ======================================================== 7. METER / STAT === */

/* .meter — a slim horizontal gauge.
   CSP-safe percentage: the *container* carries data-pct="0".."100" and the
   generated rules at the end of this file set the fill width. No style
   attribute, no JS.

     <div class="meter" data-pct="72" role="img"
          aria-label="Detection rate 72 percent">
       <span class="meter-fill"></span>
     </div>

   Always pair a meter with the number in text — the fill is the shape of the
   value, not the only way to read it. */
.meter {
  position: relative;
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}

.meter-lg { height: 10px; }
.meter-sm { height: 4px; }

.meter-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--accent);
  transition: width var(--t) ease;
}

/* Severity variants. In this product a HIGH detection rate is good, so the
   default semantic fill is --ok and the caller opts into warn/bad. */
.meter-ok   .meter-fill,
.meter-fill.is-ok   { background: var(--ok); }
.meter-warn .meter-fill,
.meter-fill.is-warn { background: var(--warn); }
.meter-bad  .meter-fill,
.meter-fill.is-bad  { background: var(--bad); }
.meter-info .meter-fill,
.meter-fill.is-info { background: var(--info); }

/* A tick marking the cohort average on a comparative meter. */
.meter-mark {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 2px;
  background: var(--fg-bright);
  opacity: .7;
}

/* ---- .stat-tile -------------------------------------------------------- */

/* Per the data-viz method: label (sentence case), value (proportional
   figures, never tabular at display size), optional signed delta against a
   named period, optional sparkline. */
.stat-tile {
  display: flex;
  flex-direction: column;
  gap: .15rem;
  min-width: 0;
  padding: .85rem 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

.stat-tile-plain {
  border: 0;
  background: none;
  padding: 0;
}

.stat-label {
  order: -1;
  color: var(--fg-faint);
  font-size: .72rem;
  font-weight: 400;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.stat-value {
  font-family: var(--display);
  font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem);
  line-height: 1.1;
  color: var(--fg-bright);
  /* Proportional figures: tabular-nums makes "121" look loose at this size. */
  font-variant-numeric: normal;
}

.stat-unit {
  color: var(--fg-faint);
  font-size: .55em;
  margin-left: .15em;
}

.stat-delta {
  display: inline-flex;
  align-items: baseline;
  gap: .25rem;
  color: var(--fg-dim);
  font-size: .76rem;
  font-variant-numeric: tabular-nums;
}

/* Direction × whether up is good. Detection rate up is GOOD; time-to-detect
   up is BAD — hence explicit .is-good / .is-bad rather than up/down. */
.stat-delta.is-good { color: var(--ok); }
.stat-delta.is-good::before { content: "\25B2"; font-size: .8em; }
.stat-delta.is-bad  { color: var(--bad); }
.stat-delta.is-bad::before  { content: "\25BC"; font-size: .8em; }
.stat-delta.is-flat { color: var(--fg-faint); }
.stat-delta.is-flat::before { content: "\2013"; }

.stat-delta-period { color: var(--fg-faint); }

.stat-tile-accent { border-left: 3px solid var(--accent); }
.stat-tile-accent .stat-value { color: var(--accent); }
.stat-tile-ok  .stat-value { color: var(--ok); }
.stat-tile-bad .stat-value { color: var(--bad); }

.stat-grid {
  display: grid;
  gap: var(--gap-sm);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

/* The one hero number a page may lead with. Same face as everything else. */
.stat-hero .stat-value {
  font-size: clamp(2.6rem, 2rem + 3vw, 4.2rem);
}


/* ============================================================= 8. GRADE === */

/* <span class="grade grade-b"><span class="sr-only">Grade </span>B</span>
   The letter is the label; colour is reinforcement only. */
.grade {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1em;
  height: 2.1em;
  border: 2px solid currentColor;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, currentColor 12%, transparent);
  color: var(--fg-dim);
  font-family: var(--display);
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1;
  text-transform: uppercase;
}

@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .grade { background: var(--surface-2); }
}

.grade-lg { font-size: 2rem; border-width: 3px; }
.grade-sm { font-size: .85rem; border-width: 1px; }

.grade-a { color: var(--grade-a); }
.grade-b { color: var(--grade-b); }
.grade-c { color: var(--grade-c); }
.grade-d { color: var(--grade-d); }
.grade-e { color: var(--grade-e); }
.grade-f { color: var(--grade-f); }

/* Grade bands legend, e.g. "A >= 90". */
.grade-scale {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: center;
  font-size: .76rem;
  color: var(--fg-faint);
}

.grade-scale > * { display: flex; align-items: center; gap: .3rem; }


/* ============================================================== 9. TABS === */

/* <div class="tabs" role="tablist"> <button class="tab is-active" …>
   ui.js turns these into a real tablist; with JS off they must be links to
   separate URLs and .tab.is-active marks the current page. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: .2rem;
  margin-bottom: var(--gap);
  padding-bottom: 0;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  scrollbar-width: thin;
}

.tab {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .45rem .8rem;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  margin-bottom: -1px;
  background: none;
  color: var(--fg-dim);
  font-family: var(--mono);
  font-size: .84rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
}

a.tab { border-bottom: 0; }

.tab:hover,
.tab:focus-visible {
  color: var(--fg-bright);
  background: var(--surface-2);
}

.tab.is-active,
.tab[aria-selected="true"] {
  color: var(--fg-bright);
  background: var(--surface);
  border-color: var(--line);
  box-shadow: inset 0 2px 0 var(--accent);
}

.tab-count {
  color: var(--fg-faint);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

.tab-panel:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px; }


/* ======================================================= 10. PAGINATION === */

.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .25rem;
  margin: var(--gap) 0 0;
  padding: 0;
  list-style: none;
  font-size: .82rem;
}

.pagination li { margin: 0; }

.pagination a,
.pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.1rem;
  padding: .28rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
  text-decoration: none;
}

.pagination a:hover,
.pagination a:focus-visible {
  color: var(--fg-bright);
  border-color: var(--accent-line);
  background: var(--surface-3);
}

.pagination [aria-current="page"] {
  color: var(--fg-bright);
  border-color: var(--accent-line);
  background: var(--accent-soft);
  font-weight: 700;
}

.pagination .is-disabled,
.pagination [aria-disabled="true"] {
  opacity: .4;
  pointer-events: none;
}

.pagination-gap {
  border: 0;
  background: none;
  color: var(--fg-faint);
  min-width: 1rem;
}

.pagination-info {
  margin-left: auto;
  color: var(--fg-faint);
  font-size: .78rem;
}


/* ==================================================== 11. CODE / CLIPBOARD = */

/* .code — inline snippet. .code-block — the framed multi-line block. */
.code {
  font-family: var(--mono);
  font-size: .92em;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-xs);
  padding: .08em .38em;
  color: var(--ok);
  overflow-wrap: anywhere;
}

.code-block {
  position: relative;
  margin: 0 0 1rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--bg);
  overflow: hidden;
}

.code-block pre {
  margin: 0;
  padding: .9rem 1rem;
  padding-right: 5.5rem;
  color: var(--fg-dim);
  font-size: .84rem;
  line-height: 1.6;
  white-space: pre;
  overflow-x: auto;
}

.code-block.is-wrap pre {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.code-block code { color: inherit; }

/* An optional header strip naming the file or shell. */
.code-block-head {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem .7rem;
  border-bottom: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--fg-faint);
  font-size: .74rem;
  letter-spacing: .04em;
}

.code-block-head + pre { padding-top: .7rem; }

/* Highlight spans a PHP-side highlighter can emit. */
.code-block .tok-cmd { color: var(--fg-bright); }
.code-block .tok-flag { color: var(--info); }
.code-block .tok-str { color: var(--ok); }
.code-block .tok-num { color: var(--warn); }
.code-block .tok-com { color: var(--fg-faint); font-style: italic; }
.code-block .tok-key { color: var(--accent); }

/* .copy-btn — sits in the corner of a .code-block. Hidden until ui.js
   enables it, because with no clipboard API it does nothing. */
.copy-btn {
  position: absolute;
  top: .45rem;
  right: .45rem;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .18rem .5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--fg-dim);
  font-family: var(--mono);
  font-size: .72rem;
  cursor: pointer;
}

.code-block-head ~ .copy-btn { top: .3rem; }

.copy-btn:hover,
.copy-btn:focus-visible {
  color: var(--fg-bright);
  border-color: var(--accent-line);
  background: var(--surface-3);
}

.copy-btn.is-copied {
  color: var(--ok);
  border-color: var(--ok-line);
  background: var(--ok-soft);
}

.copy-btn svg { width: .95em; height: .95em; }

/* .kbd — a key cap. */
.kbd {
  display: inline-block;
  padding: .05em .4em;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--fg-bright);
  font-family: var(--mono);
  font-size: .8em;
  line-height: 1.5;
  white-space: nowrap;
}


/* ========================================================= 12. DROPZONE === */

/* <label class="dropzone" for="report"> … <input class="sr-only" …> </label>
   A <label> wrapping a real file input: the click target and keyboard path
   work with JS off; ui.js only adds the drag-and-drop affordance. */
.dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: clamp(1.4rem, 5vw, 2.6rem) 1.2rem;
  border: 2px dashed var(--line);
  border-radius: var(--radius);
  background:
    repeating-linear-gradient(45deg,
      var(--surface-2) 0 10px, var(--surface) 10px 20px);
  color: var(--fg-dim);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--t) ease, background-color var(--t) ease,
              color var(--t) ease;
}

.dropzone:hover,
.dropzone:focus-within {
  border-color: var(--accent-line);
  color: var(--fg-bright);
}

.dropzone.is-dragover {
  border-color: var(--accent);
  border-style: solid;
  background: var(--accent-soft);
  color: var(--fg-bright);
  box-shadow: var(--glow-accent);
}

.dropzone.is-invalid {
  border-color: var(--bad);
  background: var(--bad-soft);
  color: var(--bad);
}

.dropzone-icon {
  width: 2.6rem;
  height: 2.6rem;
  color: var(--accent);
  opacity: .85;
}

.dropzone.is-dragover .dropzone-icon { opacity: 1; }

.dropzone-title {
  color: var(--fg-bright);
  font-weight: 700;
  font-size: .95rem;
}

.dropzone-hint { color: var(--fg-faint); font-size: .78rem; }

/* Shows the chosen filename; ui.js fills it. */
.dropzone-file {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  max-width: 100%;
  padding: .15rem .5rem;
  border: 1px solid var(--ok-line);
  border-radius: var(--radius-xs);
  background: var(--ok-soft);
  color: var(--ok);
  font-size: .78rem;
}

.dropzone-file .truncate { max-width: 22ch; }


/* ========================================================= 13. TIMELINE === */

/* An ordered run log: posture collection, then each test, then the summary.
   <ol class="timeline"><li class="timeline-item is-ok">…</li></ol> */
.timeline {
  position: relative;
  margin: 0;
  padding: 0 0 0 1.4rem;
  list-style: none;
}

/* The spine. */
.timeline::before {
  content: "";
  position: absolute;
  left: 4px;
  top: .55rem;
  bottom: .55rem;
  width: 1px;
  background: var(--line);
}

.timeline-item {
  position: relative;
  margin: 0 0 .9rem;
  padding-bottom: .9rem;
  border-bottom: 1px solid var(--line-soft);
  min-width: 0;
}

.timeline-item:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: 0;
}

/* The node. Colour comes from the status class; the shape stays constant so
   the marker itself is not the status channel. */
.timeline-item::before {
  content: "";
  position: absolute;
  left: -1.4rem;
  top: .5rem;
  width: 9px;
  height: 9px;
  border: 2px solid var(--fg-faint);
  border-radius: 50%;
  /* 2px surface ring so the node stays legible over the spine. */
  box-shadow: 0 0 0 2px var(--surface);
  background: var(--surface);
}

.timeline-item.is-ok::before   { border-color: var(--ok);   background: var(--ok); }
.timeline-item.is-warn::before { border-color: var(--warn); background: var(--warn); }
.timeline-item.is-bad::before  { border-color: var(--bad);  background: var(--bad); }
.timeline-item.is-info::before { border-color: var(--info); background: var(--info); }

.timeline-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem .7rem;
  min-width: 0;
}

.timeline-title {
  color: var(--fg-bright);
  font-weight: 700;
  font-size: .92rem;
  min-width: 0;
}

.timeline-time {
  color: var(--fg-faint);
  font-size: .74rem;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
}

.timeline-body {
  margin: .3rem 0 0;
  color: var(--fg-dim);
  font-size: .84rem;
}

.timeline-body > :last-child { margin-bottom: 0; }

/* A key/value detail list, used inside timeline items and posture cards. */
.kv {
  display: grid;
  grid-template-columns: minmax(9rem, auto) minmax(0, 1fr);
  gap: .3rem .9rem;
  margin: 0;
  font-size: .84rem;
}

.kv dt { color: var(--fg-faint); }
.kv dd { margin: 0; color: var(--fg); overflow-wrap: anywhere; }

@media (max-width: 480px) {
  .kv { grid-template-columns: minmax(0, 1fr); gap: 0 0; }
  .kv dd { margin-bottom: .45rem; }
}


/* ==================================================== 14. EMPTY / SPINNER === */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .6rem;
  padding: clamp(1.6rem, 6vw, 3.2rem) 1.2rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: var(--bg-alt);
  color: var(--fg-dim);
  text-align: center;
}

.empty-state-icon {
  width: 3rem;
  height: 3rem;
  color: var(--fg-faint);
  opacity: .7;
}

.empty-state-title {
  color: var(--fg-bright);
  font-family: var(--display);
  font-size: 1.1rem;
}

.empty-state p { margin: 0; max-width: 46ch; font-size: .86rem; }

/* An ASCII-art figure for a richer empty state; decorative. */
.empty-state-art {
  margin: 0;
  color: var(--fg-faint);
  font-size: .7rem;
  line-height: 1.15;
  opacity: .55;
  white-space: pre;
  overflow-x: auto;
}

/* .spinner — a rotating bracket ring. Under prefers-reduced-motion the
   rotation stops and it becomes a static ring, so it never conveys progress
   by motion alone; always pair it with a text status. */
.spinner {
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  vertical-align: -.15em;
  border: 2px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: sc-spin .8s linear infinite;
}

.spinner-lg { width: 2rem; height: 2rem; border-width: 3px; }

@keyframes sc-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .spinner {
    animation: none;
    border-top-color: var(--accent);
    opacity: .8;
  }
}

.is-busy { opacity: .55; }

/* Refetch: hold the previous render rather than flashing a skeleton. */
.is-stale { opacity: .5; transition: opacity var(--t) ease; }


/* ========================================================== 15. TOOLTIP === */

/* .tooltip — a hover/focus hint on a short trigger.

     <span class="tooltip" tabindex="0" aria-describedby="tt-1">
       detection rate
       <span class="tooltip-body" role="tooltip" id="tt-1">…</span>
     </span>

   The text lives in the DOM (not in a CSS content: attr()) so it is in the
   accessibility tree and reachable by keyboard. A tooltip never carries a
   value that is not also readable elsewhere. */
.tooltip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: .2rem;
  border-bottom: 1px dotted var(--fg-faint);
  cursor: help;
}

.tooltip-body {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  z-index: 60;
  width: max-content;
  max-width: min(30ch, 78vw);
  padding: .4rem .6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--fg);
  box-shadow: var(--shadow-pop);
  font-size: .76rem;
  font-weight: 400;
  line-height: 1.45;
  text-align: left;
  white-space: normal;
  transform: translateX(-50%);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--t) ease, visibility var(--t) ease;
  pointer-events: none;
}

.tooltip:hover > .tooltip-body,
.tooltip:focus-visible > .tooltip-body,
.tooltip:focus-within > .tooltip-body,
.tooltip.is-open > .tooltip-body {
  opacity: 1;
  visibility: visible;
}

/* Anchor to the left/right edge when the trigger is near a viewport border. */
.tooltip-body.is-start { left: 0; transform: none; }
.tooltip-body.is-end   { left: auto; right: 0; transform: none; }

/* A small circled "?" trigger. */
.tooltip-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.15em;
  height: 1.15em;
  border: 1px solid var(--fg-faint);
  border-radius: 50%;
  color: var(--fg-faint);
  font-size: .7rem;
  line-height: 1;
}


/* ====================================================== 16. MISC PIECES === */

/* A hero block: banner, lede, prompt line, actions. */
.hero {
  position: relative;
  padding: clamp(1.2rem, 4vw, 2.6rem) 0 1rem;
}

.hero-lede {
  max-width: 64ch;
  color: var(--fg);
  font-size: clamp(1rem, .95rem + .35vw, 1.14rem);
}

.hero-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  margin-bottom: .8rem;
  color: var(--fg-dim);
  font-size: .9rem;
}

/* A download row for the client binaries. */
.dl-list {
  display: grid;
  gap: .4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.dl-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem .8rem;
  padding: .5rem .7rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  font-size: .84rem;
  min-width: 0;
}

.dl-os {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  min-width: 9rem;
  color: var(--fg-bright);
  font-weight: 700;
}

.dl-os svg { width: 1.1em; height: 1.1em; flex: none; }
.dl-arch { color: var(--fg-faint); font-size: .76rem; }
.dl-size { color: var(--fg-faint); font-size: .76rem; margin-left: auto; font-variant-numeric: tabular-nums; }
.dl-hash { flex: 1 1 100%; color: var(--fg-faint); font-size: .68rem; overflow-wrap: anywhere; }

/* Ordered "how to run it" steps. */
.steps {
  counter-reset: step;
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .9rem;
}

.steps > li {
  counter-increment: step;
  position: relative;
  padding-left: 2.4rem;
  margin: 0;
  min-width: 0;
}

.steps > li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: .05rem;
  width: 1.8rem;
  color: var(--accent);
  font-family: var(--display);
  font-size: .9rem;
  text-align: right;
}

.step-title {
  display: block;
  color: var(--fg-bright);
  font-weight: 700;
  margin-bottom: .2rem;
}

/* A scrolling marquee-free "live" indicator dot. */
.dot {
  display: inline-block;
  width: .5em;
  height: .5em;
  border-radius: 50%;
  background: var(--fg-faint);
  vertical-align: .05em;
}

.dot-ok   { background: var(--ok);   box-shadow: 0 0 0 3px var(--ok-soft); }
.dot-warn { background: var(--warn); box-shadow: 0 0 0 3px var(--warn-soft); }
.dot-bad  { background: var(--bad);  box-shadow: 0 0 0 3px var(--bad-soft); }

/* A maintenance / danger ribbon for the top of the page. */
.ribbon {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: .4rem .8rem;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: .8rem;
  font-weight: 700;
  text-align: center;
}

.ribbon a { color: var(--accent-ink); border-bottom-color: currentColor; }

/* Modular content well with the terminal window look, for prose pages. */
.term-window {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  margin-bottom: var(--gap);
}

.prose { max-width: 74ch; }
.prose h2 { margin-top: 1.9rem; }
.prose h3 { margin-top: 1.4rem; }
.prose > :first-child { margin-top: 0; }
.prose ul { padding-left: 1.2rem; }
.prose li { margin: .3rem 0; }

/* Forced-colors: drop decorative fills so the OS palette can take over,
   but keep every border so structure survives. */
@media (forced-colors: active) {
  .card,
  .stat-tile,
  .badge,
  .alert,
  .btn,
  .table-wrap,
  .dropzone,
  .empty-state { border: 1px solid CanvasText; }

  .badge::before,
  .alert::before { forced-color-adjust: none; }

  .meter { border: 1px solid CanvasText; }
  .meter-fill { background: CanvasText; }
}


/* ============================== 17. GENERATED METER WIDTHS (CSP-safe) =====
   .meter[data-pct="N"] .meter-fill { width: N%; }  for N = 0..100.
   Generated, not hand-typed. The same attribute pattern also drives
   .bar-h[data-pct] in charts.css, which shares these rules.
   ========================================================================= */
.meter[data-pct="0"] .meter-fill,
.bar-h[data-pct="0"] .bar-h-fill { width: 0%; }
.meter[data-pct="1"] .meter-fill,
.bar-h[data-pct="1"] .bar-h-fill { width: 1%; }
.meter[data-pct="2"] .meter-fill,
.bar-h[data-pct="2"] .bar-h-fill { width: 2%; }
.meter[data-pct="3"] .meter-fill,
.bar-h[data-pct="3"] .bar-h-fill { width: 3%; }
.meter[data-pct="4"] .meter-fill,
.bar-h[data-pct="4"] .bar-h-fill { width: 4%; }
.meter[data-pct="5"] .meter-fill,
.bar-h[data-pct="5"] .bar-h-fill { width: 5%; }
.meter[data-pct="6"] .meter-fill,
.bar-h[data-pct="6"] .bar-h-fill { width: 6%; }
.meter[data-pct="7"] .meter-fill,
.bar-h[data-pct="7"] .bar-h-fill { width: 7%; }
.meter[data-pct="8"] .meter-fill,
.bar-h[data-pct="8"] .bar-h-fill { width: 8%; }
.meter[data-pct="9"] .meter-fill,
.bar-h[data-pct="9"] .bar-h-fill { width: 9%; }
.meter[data-pct="10"] .meter-fill,
.bar-h[data-pct="10"] .bar-h-fill { width: 10%; }
.meter[data-pct="11"] .meter-fill,
.bar-h[data-pct="11"] .bar-h-fill { width: 11%; }
.meter[data-pct="12"] .meter-fill,
.bar-h[data-pct="12"] .bar-h-fill { width: 12%; }
.meter[data-pct="13"] .meter-fill,
.bar-h[data-pct="13"] .bar-h-fill { width: 13%; }
.meter[data-pct="14"] .meter-fill,
.bar-h[data-pct="14"] .bar-h-fill { width: 14%; }
.meter[data-pct="15"] .meter-fill,
.bar-h[data-pct="15"] .bar-h-fill { width: 15%; }
.meter[data-pct="16"] .meter-fill,
.bar-h[data-pct="16"] .bar-h-fill { width: 16%; }
.meter[data-pct="17"] .meter-fill,
.bar-h[data-pct="17"] .bar-h-fill { width: 17%; }
.meter[data-pct="18"] .meter-fill,
.bar-h[data-pct="18"] .bar-h-fill { width: 18%; }
.meter[data-pct="19"] .meter-fill,
.bar-h[data-pct="19"] .bar-h-fill { width: 19%; }
.meter[data-pct="20"] .meter-fill,
.bar-h[data-pct="20"] .bar-h-fill { width: 20%; }
.meter[data-pct="21"] .meter-fill,
.bar-h[data-pct="21"] .bar-h-fill { width: 21%; }
.meter[data-pct="22"] .meter-fill,
.bar-h[data-pct="22"] .bar-h-fill { width: 22%; }
.meter[data-pct="23"] .meter-fill,
.bar-h[data-pct="23"] .bar-h-fill { width: 23%; }
.meter[data-pct="24"] .meter-fill,
.bar-h[data-pct="24"] .bar-h-fill { width: 24%; }
.meter[data-pct="25"] .meter-fill,
.bar-h[data-pct="25"] .bar-h-fill { width: 25%; }
.meter[data-pct="26"] .meter-fill,
.bar-h[data-pct="26"] .bar-h-fill { width: 26%; }
.meter[data-pct="27"] .meter-fill,
.bar-h[data-pct="27"] .bar-h-fill { width: 27%; }
.meter[data-pct="28"] .meter-fill,
.bar-h[data-pct="28"] .bar-h-fill { width: 28%; }
.meter[data-pct="29"] .meter-fill,
.bar-h[data-pct="29"] .bar-h-fill { width: 29%; }
.meter[data-pct="30"] .meter-fill,
.bar-h[data-pct="30"] .bar-h-fill { width: 30%; }
.meter[data-pct="31"] .meter-fill,
.bar-h[data-pct="31"] .bar-h-fill { width: 31%; }
.meter[data-pct="32"] .meter-fill,
.bar-h[data-pct="32"] .bar-h-fill { width: 32%; }
.meter[data-pct="33"] .meter-fill,
.bar-h[data-pct="33"] .bar-h-fill { width: 33%; }
.meter[data-pct="34"] .meter-fill,
.bar-h[data-pct="34"] .bar-h-fill { width: 34%; }
.meter[data-pct="35"] .meter-fill,
.bar-h[data-pct="35"] .bar-h-fill { width: 35%; }
.meter[data-pct="36"] .meter-fill,
.bar-h[data-pct="36"] .bar-h-fill { width: 36%; }
.meter[data-pct="37"] .meter-fill,
.bar-h[data-pct="37"] .bar-h-fill { width: 37%; }
.meter[data-pct="38"] .meter-fill,
.bar-h[data-pct="38"] .bar-h-fill { width: 38%; }
.meter[data-pct="39"] .meter-fill,
.bar-h[data-pct="39"] .bar-h-fill { width: 39%; }
.meter[data-pct="40"] .meter-fill,
.bar-h[data-pct="40"] .bar-h-fill { width: 40%; }
.meter[data-pct="41"] .meter-fill,
.bar-h[data-pct="41"] .bar-h-fill { width: 41%; }
.meter[data-pct="42"] .meter-fill,
.bar-h[data-pct="42"] .bar-h-fill { width: 42%; }
.meter[data-pct="43"] .meter-fill,
.bar-h[data-pct="43"] .bar-h-fill { width: 43%; }
.meter[data-pct="44"] .meter-fill,
.bar-h[data-pct="44"] .bar-h-fill { width: 44%; }
.meter[data-pct="45"] .meter-fill,
.bar-h[data-pct="45"] .bar-h-fill { width: 45%; }
.meter[data-pct="46"] .meter-fill,
.bar-h[data-pct="46"] .bar-h-fill { width: 46%; }
.meter[data-pct="47"] .meter-fill,
.bar-h[data-pct="47"] .bar-h-fill { width: 47%; }
.meter[data-pct="48"] .meter-fill,
.bar-h[data-pct="48"] .bar-h-fill { width: 48%; }
.meter[data-pct="49"] .meter-fill,
.bar-h[data-pct="49"] .bar-h-fill { width: 49%; }
.meter[data-pct="50"] .meter-fill,
.bar-h[data-pct="50"] .bar-h-fill { width: 50%; }
.meter[data-pct="51"] .meter-fill,
.bar-h[data-pct="51"] .bar-h-fill { width: 51%; }
.meter[data-pct="52"] .meter-fill,
.bar-h[data-pct="52"] .bar-h-fill { width: 52%; }
.meter[data-pct="53"] .meter-fill,
.bar-h[data-pct="53"] .bar-h-fill { width: 53%; }
.meter[data-pct="54"] .meter-fill,
.bar-h[data-pct="54"] .bar-h-fill { width: 54%; }
.meter[data-pct="55"] .meter-fill,
.bar-h[data-pct="55"] .bar-h-fill { width: 55%; }
.meter[data-pct="56"] .meter-fill,
.bar-h[data-pct="56"] .bar-h-fill { width: 56%; }
.meter[data-pct="57"] .meter-fill,
.bar-h[data-pct="57"] .bar-h-fill { width: 57%; }
.meter[data-pct="58"] .meter-fill,
.bar-h[data-pct="58"] .bar-h-fill { width: 58%; }
.meter[data-pct="59"] .meter-fill,
.bar-h[data-pct="59"] .bar-h-fill { width: 59%; }
.meter[data-pct="60"] .meter-fill,
.bar-h[data-pct="60"] .bar-h-fill { width: 60%; }
.meter[data-pct="61"] .meter-fill,
.bar-h[data-pct="61"] .bar-h-fill { width: 61%; }
.meter[data-pct="62"] .meter-fill,
.bar-h[data-pct="62"] .bar-h-fill { width: 62%; }
.meter[data-pct="63"] .meter-fill,
.bar-h[data-pct="63"] .bar-h-fill { width: 63%; }
.meter[data-pct="64"] .meter-fill,
.bar-h[data-pct="64"] .bar-h-fill { width: 64%; }
.meter[data-pct="65"] .meter-fill,
.bar-h[data-pct="65"] .bar-h-fill { width: 65%; }
.meter[data-pct="66"] .meter-fill,
.bar-h[data-pct="66"] .bar-h-fill { width: 66%; }
.meter[data-pct="67"] .meter-fill,
.bar-h[data-pct="67"] .bar-h-fill { width: 67%; }
.meter[data-pct="68"] .meter-fill,
.bar-h[data-pct="68"] .bar-h-fill { width: 68%; }
.meter[data-pct="69"] .meter-fill,
.bar-h[data-pct="69"] .bar-h-fill { width: 69%; }
.meter[data-pct="70"] .meter-fill,
.bar-h[data-pct="70"] .bar-h-fill { width: 70%; }
.meter[data-pct="71"] .meter-fill,
.bar-h[data-pct="71"] .bar-h-fill { width: 71%; }
.meter[data-pct="72"] .meter-fill,
.bar-h[data-pct="72"] .bar-h-fill { width: 72%; }
.meter[data-pct="73"] .meter-fill,
.bar-h[data-pct="73"] .bar-h-fill { width: 73%; }
.meter[data-pct="74"] .meter-fill,
.bar-h[data-pct="74"] .bar-h-fill { width: 74%; }
.meter[data-pct="75"] .meter-fill,
.bar-h[data-pct="75"] .bar-h-fill { width: 75%; }
.meter[data-pct="76"] .meter-fill,
.bar-h[data-pct="76"] .bar-h-fill { width: 76%; }
.meter[data-pct="77"] .meter-fill,
.bar-h[data-pct="77"] .bar-h-fill { width: 77%; }
.meter[data-pct="78"] .meter-fill,
.bar-h[data-pct="78"] .bar-h-fill { width: 78%; }
.meter[data-pct="79"] .meter-fill,
.bar-h[data-pct="79"] .bar-h-fill { width: 79%; }
.meter[data-pct="80"] .meter-fill,
.bar-h[data-pct="80"] .bar-h-fill { width: 80%; }
.meter[data-pct="81"] .meter-fill,
.bar-h[data-pct="81"] .bar-h-fill { width: 81%; }
.meter[data-pct="82"] .meter-fill,
.bar-h[data-pct="82"] .bar-h-fill { width: 82%; }
.meter[data-pct="83"] .meter-fill,
.bar-h[data-pct="83"] .bar-h-fill { width: 83%; }
.meter[data-pct="84"] .meter-fill,
.bar-h[data-pct="84"] .bar-h-fill { width: 84%; }
.meter[data-pct="85"] .meter-fill,
.bar-h[data-pct="85"] .bar-h-fill { width: 85%; }
.meter[data-pct="86"] .meter-fill,
.bar-h[data-pct="86"] .bar-h-fill { width: 86%; }
.meter[data-pct="87"] .meter-fill,
.bar-h[data-pct="87"] .bar-h-fill { width: 87%; }
.meter[data-pct="88"] .meter-fill,
.bar-h[data-pct="88"] .bar-h-fill { width: 88%; }
.meter[data-pct="89"] .meter-fill,
.bar-h[data-pct="89"] .bar-h-fill { width: 89%; }
.meter[data-pct="90"] .meter-fill,
.bar-h[data-pct="90"] .bar-h-fill { width: 90%; }
.meter[data-pct="91"] .meter-fill,
.bar-h[data-pct="91"] .bar-h-fill { width: 91%; }
.meter[data-pct="92"] .meter-fill,
.bar-h[data-pct="92"] .bar-h-fill { width: 92%; }
.meter[data-pct="93"] .meter-fill,
.bar-h[data-pct="93"] .bar-h-fill { width: 93%; }
.meter[data-pct="94"] .meter-fill,
.bar-h[data-pct="94"] .bar-h-fill { width: 94%; }
.meter[data-pct="95"] .meter-fill,
.bar-h[data-pct="95"] .bar-h-fill { width: 95%; }
.meter[data-pct="96"] .meter-fill,
.bar-h[data-pct="96"] .bar-h-fill { width: 96%; }
.meter[data-pct="97"] .meter-fill,
.bar-h[data-pct="97"] .bar-h-fill { width: 97%; }
.meter[data-pct="98"] .meter-fill,
.bar-h[data-pct="98"] .bar-h-fill { width: 98%; }
.meter[data-pct="99"] .meter-fill,
.bar-h[data-pct="99"] .bar-h-fill { width: 99%; }
.meter[data-pct="100"] .meter-fill,
.bar-h[data-pct="100"] .bar-h-fill { width: 100%; }
