/* ==========================================================================
   THUGS(red) Security Check — base.css
   --------------------------------------------------------------------------
   Reset, design tokens, typography, layout primitives, utilities.
   Hand-written; no framework, no build step, no CDN, no inline styles.

   Load order matters:   base.css  →  components.css  →  charts.css

   The visual language deliberately mirrors the community main site
   (https://thugs.red): near-black CRT ground, monospace everything, the
   THUGS red accent, shell-prompt motifs, block-character rules and
   [bracketed] text affordances.

   TOKEN CONTRACT
   Every colour, radius, gap and font lives on :root as a custom property, so
   the palette can be retuned in exactly one place. Dark is the default and
   primary theme; [data-theme="light"] is the readable alternative that
   html/js/ui.js writes onto <html> and persists to localStorage.

   Contrast: every text/status token clears WCAG AA (4.5:1) against every
   surface token in its own theme. See html/css/README.md for the measured
   ratios and for the data-viz palette derivation.
   ========================================================================== */

/* ============================================================ 1. TOKENS === */

:root {
  /* ---- surfaces: darkest ground → highest raised layer ---- */
  --bg:          #08090b;   /* page ground                                  */
  --bg-alt:      #0c0f13;   /* inset wells: code blocks, table stripes      */
  --surface:     #0e1217;   /* cards, panels — the chart surface            */
  --surface-2:   #131820;   /* card headers, table headers, chips           */
  --surface-3:   #171e27;   /* hover state of a raised layer                */

  /* ---- borders ---- */
  --line:        #1e2630;   /* structural hairline                          */
  --line-soft:   #161c24;   /* internal divider (table rows, list splits)   */

  /* ---- text: 17.3 / 12.8 / 7.5 / 5.5 on --surface ---- */
  --fg-bright:   #f2f6fa;   /* headings, emphasised values                  */
  --fg:          #cfd6de;   /* body                                         */
  --fg-dim:      #9aa4b2;   /* secondary body, labels                       */
  --fg-faint:    #828c9a;   /* captions, axis ticks, metadata               */

  /* ---- brand accent ---- */
  --accent:      #ff3b47;   /* THUGS red — 5.34:1 on --surface             */
  --accent-ink:  #08090b;   /* text drawn ON an accent fill                 */
  --accent-soft: rgba(255, 59, 71, 0.14);
  --accent-line: rgba(255, 59, 71, 0.45);

  /* ---- semantic status ------------------------------------------------
     IMPORTANT, AND INVERTED FROM THE USUAL INSTINCT:
       --ok   "detected"      the customer's tooling caught the simulated
                              attack. GOOD NEWS. green.
       --bad  "not detected"  the attack completed unimpeded. BAD NEWS.
                              red. This is the finding that matters.
       --warn "blocked" / "degraded" — prevented up front, or a posture
                              control that is present but unhealthy. amber.
       --info "skipped" / "error" — did not count either way. cyan.
       --neutral              no data / not applicable. grey.
     Status colour NEVER carries meaning alone: every status token ships with
     a bracketed sigil and a text label (see .badge / .alert in
     components.css). --bad is a step off --accent on purpose, so brand
     chrome and status ink are never the same ink.
     -------------------------------------------------------------------- */
  --ok:          #3ddc84;
  --warn:        #ffcc55;
  --bad:         #ff5865;
  --info:        #4fe3d2;
  --neutral:     #9aa4b2;

  --ok-soft:      rgba(61, 220, 132, 0.12);
  --warn-soft:    rgba(255, 204, 85, 0.12);
  --bad-soft:     rgba(255, 88, 101, 0.12);
  --info-soft:    rgba(79, 227, 210, 0.12);
  --neutral-soft: rgba(154, 164, 178, 0.10);

  --ok-line:      rgba(61, 220, 132, 0.45);
  --warn-line:    rgba(255, 204, 85, 0.45);
  --bad-line:     rgba(255, 88, 101, 0.45);
  --info-line:    rgba(79, 227, 210, 0.40);
  --neutral-line: rgba(154, 164, 178, 0.35);

  /* ---- grade bands (A >= 90, B >= 75, C >= 60, D >= 40, F < 40) --------
     An ordered good→critical scale. The letter itself is the label, so hue
     never carries the grade alone. --grade-e exists for completeness even
     though docs/REPORT_SCHEMA.md skips E.                               */
  --grade-a: #3ddc84;
  --grade-b: #8fdc4a;
  --grade-c: #ffcc55;
  --grade-d: #ff9d3d;
  --grade-e: #ff7a4d;
  --grade-f: #ff5865;

  /* ---- focus ---- */
  --focus:      #4fe3d2;
  --focus-ring: 0 0 0 3px rgba(79, 227, 210, 0.35);

  /* ---- glows: decorative only, never load-bearing ---- */
  --glow-accent: 0 0 0 1px rgba(255, 59, 71, 0.35), 0 0 22px -6px rgba(255, 59, 71, 0.5);
  --glow-ok:     0 0 0 1px rgba(61, 220, 132, 0.30), 0 0 20px -8px rgba(61, 220, 132, 0.45);
  --shadow-card: 0 18px 40px -28px rgba(0, 0, 0, 0.9);
  --shadow-pop:  0 12px 28px -12px rgba(0, 0, 0, 0.85);

  /* ---- type ---- */
  --mono: ui-monospace, "SFMono-Regular", "JetBrains Mono", "Fira Code",
          "Cascadia Mono", "Cascadia Code", Menlo, Consolas,
          "Liberation Mono", "DejaVu Sans Mono", monospace;
  /* Display face for brand, banners and hero figures. Kept in the mono
     family on purpose — the terminal illusion breaks the moment a
     proportional face appears. */
  --display: "Share Tech Mono", "Cascadia Mono", ui-monospace,
             "JetBrains Mono", Menlo, Consolas, monospace;

  --fs-base: 15px;
  --lh-base: 1.65;

  /* ---- geometry ---- */
  --radius:    6px;
  --radius-sm: 4px;
  --radius-xs: 3px;
  --gap:       1.25rem;
  --gap-sm:    0.6rem;
  --wrap:      1180px;
  --wrap-wide: 1560px;
  --header-h:  58px;

  /* ---- motion (zeroed under prefers-reduced-motion, below) ---- */
  --t-fast: 0.12s;
  --t:      0.18s;
  --blink:  1.15s;

  color-scheme: dark;
}

/* --------------------------------------------------------- light theme ----
   Opt-in only: dark is the primary theme, and ui.js stamps
   data-theme="light" on <html> when the visitor asks for it. A "paper
   terminal" — same structure, same monospace, ink on bone instead of
   phosphor on black. Every ratio below is re-measured, not flipped.        */

:root[data-theme="light"] {
  --bg:          #e8eaee;
  --bg-alt:      #eef0f4;
  --surface:     #fbfcfd;
  --surface-2:   #f2f4f8;
  --surface-3:   #e6e9ef;

  --line:        #c9d0da;
  --line-soft:   #dde2e9;

  --fg-bright:   #0b0e12;
  --fg:          #1c2128;
  --fg-dim:      #4b5563;
  --fg-faint:    #5d6774;

  --accent:      #d1122b;
  --accent-ink:  #fbfcfd;
  --accent-soft: rgba(209, 18, 43, 0.09);
  --accent-line: rgba(209, 18, 43, 0.40);

  --ok:          #0a7a41;
  --warn:        #7d5600;
  --bad:         #c1121f;
  --info:        #0b6d7a;
  --neutral:     #59636f;

  --ok-soft:      rgba(10, 122, 65, 0.10);
  --warn-soft:    rgba(125, 86, 0, 0.10);
  --bad-soft:     rgba(193, 18, 31, 0.09);
  --info-soft:    rgba(11, 109, 122, 0.09);
  --neutral-soft: rgba(89, 99, 111, 0.08);

  --ok-line:      rgba(10, 122, 65, 0.40);
  --warn-line:    rgba(125, 86, 0, 0.40);
  --bad-line:     rgba(193, 18, 31, 0.38);
  --info-line:    rgba(11, 109, 122, 0.35);
  --neutral-line: rgba(89, 99, 111, 0.32);

  --grade-a: #0a7a41;
  --grade-b: #4d6f00;
  --grade-c: #7d5600;
  --grade-d: #8a4a05;
  --grade-e: #a83417;
  --grade-f: #c1121f;

  --focus:      #0b6d7a;
  --focus-ring: 0 0 0 3px rgba(11, 109, 122, 0.30);

  --glow-accent: 0 0 0 1px rgba(209, 18, 43, 0.30), 0 0 20px -8px rgba(209, 18, 43, 0.30);
  --glow-ok:     0 0 0 1px rgba(10, 122, 65, 0.30), 0 0 20px -10px rgba(10, 122, 65, 0.30);
  --shadow-card: 0 14px 32px -26px rgba(11, 14, 18, 0.55);
  --shadow-pop:  0 12px 26px -14px rgba(11, 14, 18, 0.35);

  color-scheme: light;
}


/* ============================================================= 2. RESET === */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  tab-size: 4;
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: var(--bg);
  /* Two very wide, very faint radial washes: the CRT bloom. Purely
     decorative, fixed so it does not scroll with content. */
  background-image:
    radial-gradient(1100px 620px at 78% -12%, var(--accent-soft), transparent 62%),
    radial-gradient(900px 520px at 6% 4%, var(--info-soft), transparent 62%);
  background-attachment: fixed;
  background-repeat: no-repeat;
  color: var(--fg);
  font-family: var(--mono);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img,
svg,
video,
canvas { max-width: 100%; }

img,
video { height: auto; }

svg { vertical-align: middle; }

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
  margin: 0;
}

button { background: none; border: 0; }

fieldset { border: 1px solid var(--line); border-radius: var(--radius); margin: 0 0 var(--gap); padding: 1rem 1.1rem; }
legend { padding: 0 .4rem; color: var(--fg-dim); font-size: .78rem; letter-spacing: .08em; text-transform: uppercase; }

/* Reduced motion: kill the blink, the scroll easing and every transition.
   The blinking cursor is the one thing on this site that would otherwise
   animate forever. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  :root { --t-fast: 1ms; --t: 1ms; }
}


/* ======================================================== 3. TYPOGRAPHY === */

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 .6em;
  color: var(--fg-bright);
  line-height: 1.25;
  font-weight: 700;
}

h1, h2 {
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: .02em;
}

h1 { font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.15rem); }
h2 { font-size: clamp(1.2rem, 1rem + .8vw, 1.5rem); }
h3 { font-size: 1.08rem; }
h4 { font-size: 1rem; color: var(--fg); }
h5, h6 {
  font-size: .8rem;
  color: var(--fg-faint);
  letter-spacing: .08em;
  text-transform: uppercase;
}

p { margin: 0 0 1rem; }

ul, ol { margin: 0 0 1rem; padding-left: 1.3rem; }
li { margin: .2rem 0; }
li::marker { color: var(--accent); }

dl { margin: 0 0 1rem; }
dt { color: var(--fg-faint); font-size: .82rem; }
dd { margin: 0 0 .5rem; }

strong, b { color: var(--fg-bright); font-weight: 700; }
em, i { font-style: italic; color: var(--fg-bright); }
small { font-size: .8rem; }

abbr[title] {
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  cursor: help;
}

/* Links read as terminal output: cyan, dotted-underlined, red on hover. */
a {
  color: var(--info);
  text-decoration: none;
  border-bottom: 1px dotted var(--info-line);
  transition: color var(--t-fast) ease, border-color var(--t-fast) ease,
              background-color var(--t-fast) ease;
}

a:hover,
a:focus-visible {
  color: var(--fg-bright);
  border-bottom-color: var(--accent);
}

a:visited { color: var(--info); }

/* A single, consistent, always-visible focus ring. Keyboard only —
   :focus-visible, never :focus — so a mouse click does not leave a ring. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-xs);
}

/* Forced-colors / Windows high contrast: let the OS win, but keep the ring. */
@media (forced-colors: active) {
  :focus-visible { outline: 2px solid Highlight; }
}

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

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

/* ---- inline code / preformatted ---- */

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

code {
  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;
}

pre {
  margin: 0 0 1rem;
  overflow-x: auto;
}

pre code {
  background: none;
  border: 0;
  padding: 0;
  color: inherit;
  font-size: inherit;
}

/* Tabular figures wherever numbers must line up in a column. */
table, .table, .tabular { font-variant-numeric: tabular-nums; }


/* =================================================== 4. LAYOUT PRIMITIVES = */

/* ---- .wrap: the centred content column --------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(1rem, 3vw, 2rem);
}

.wrap-wide { max-width: var(--wrap-wide); }

/* ---- skip link: first tabbable element on every page ------------------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  padding: .6rem 1rem;
  background: var(--accent);
  color: var(--accent-ink);
  border: 0;
  font-weight: 700;
}

.skip-link:focus { left: 0; }

/* ---- .site-header ------------------------------------------------------ */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 84%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

/* No color-mix support: fall back to the opaque ground rather than to
   unreadable transparency. */
@supports not (background: color-mix(in srgb, red 50%, transparent)) {
  .site-header { background: var(--bg); }
}

.site-header > .wrap {
  max-width: var(--wrap-wide);
  display: flex;
  align-items: center;
  gap: .8rem;
  min-height: var(--header-h);
  flex-wrap: wrap;
}

/* ---- .site-brand ------------------------------------------------------- */

.site-brand {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  border: 0;
  font-family: var(--display);
  font-size: 1.22rem;
  font-weight: 400;
  letter-spacing: .04em;
  white-space: nowrap;
  color: var(--fg-bright);
}

.site-brand:hover,
.site-brand:focus-visible { color: var(--fg-bright); }

.site-brand svg,
.site-brand img {
  width: 1.5em;
  height: 1.5em;
  flex: none;
}

/* Markup: THUGS<span class="site-brand-red">(red)</span> */
.site-brand-red {
  color: var(--accent);
  text-shadow: 0 0 14px var(--accent-soft);
}

.site-brand-sub {
  color: var(--fg-faint);
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* ---- .site-nav --------------------------------------------------------- */

.site-nav { margin-left: auto; }

.site-nav ul {
  display: flex;
  flex-wrap: wrap;
  gap: .15rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-nav li { margin: 0; }

.site-nav a {
  display: inline-block;
  padding: .3rem .5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--fg-dim);
  font-size: .84rem;
  white-space: nowrap;
}

/* A leading slash marks each nav item as a path segment. Generated content,
   so it never lands in the accessible name. */
.site-nav a::before {
  content: "/";
  color: var(--fg-faint);
  margin-right: 1px;
}

.site-nav a:hover,
.site-nav a:focus-visible {
  color: var(--fg-bright);
  background: var(--surface-2);
  border-color: var(--line);
}

.site-nav a.is-active {
  color: var(--fg-bright);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}

.site-nav a.is-active::before { color: var(--accent); }

/* Mobile: the toggle is hidden on desktop and the list is always open.
   With JS off the list stays visible (see .site-nav[hidden] never being
   set by CSS), so navigation never depends on scripting. */
.nav-toggle { display: none; }

@media (max-width: 860px) {
  .site-header > .wrap { flex-wrap: wrap; }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    margin-left: auto;
    padding: .3rem .6rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--fg);
    font-size: .82rem;
    cursor: pointer;
  }

  .nav-toggle[aria-expanded="true"] {
    border-color: var(--accent-line);
    color: var(--fg-bright);
  }

  /* The nav takes a full row of its own, so anything that should stay beside
     the menu button needs a lower order than it — otherwise the nav's
     flex-basis:100% forces a line break and the theme toggle lands on a
     third row all by itself. */
  .site-nav {
    order: 3;
    flex-basis: 100%;
    margin-left: 0;
  }

  .nav-toggle { order: 1; }
  .theme-toggle { order: 2; }

  /* ui.js sets hidden on the container; without JS it is never set. */
  .site-nav ul {
    flex-direction: column;
    gap: 0;
    padding-block: .4rem;
    border-top: 1px solid var(--line-soft);
  }

  .site-nav a {
    display: block;
    padding: .5rem .4rem;
    font-size: .9rem;
  }
}

/* Below ~520px the brand's "SECURITY CHECK" strapline plus the menu button
   fill the row on their own and push the theme toggle onto a line of its own.
   The strapline is the least load-bearing thing there, so it goes. */
@media (max-width: 520px) {
  .site-brand-sub { display: none; }
  .site-brand { font-size: 1.1rem; }
}

/* ---- .site-main -------------------------------------------------------- */

.site-main {
  flex: 1 0 auto;
  padding-block: 2rem 4rem;
}

/* Anchor targets clear the sticky header. */
.site-main [id] { scroll-margin-top: calc(var(--header-h) + 26px); }

/* ---- .site-footer ------------------------------------------------------ */

.site-footer {
  flex: none;
  border-top: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  padding-block: 1.6rem 1.2rem;
  font-size: .85rem;
}

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

.site-footer-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 1.4rem;
  margin-bottom: 1.2rem;
}

.site-footer h2,
.site-footer h3,
.site-footer .site-footer-head {
  margin: 0 0 .4rem;
  font-family: var(--mono);
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--accent);
}

.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin: .15rem 0; }

.site-footer a {
  color: var(--fg-dim);
  border-bottom: 0;
}

.site-footer a:hover,
.site-footer a:focus-visible { color: var(--fg-bright); }

.site-footer-bottom {
  margin: 0;
  padding-top: .9rem;
  border-top: 1px solid var(--line-soft);
  color: var(--fg-dim);
  font-size: .8rem;
  overflow-wrap: anywhere;
}

/* ---- .prompt / .cursor ------------------------------------------------- */

/* <span class="prompt">root@thugsred:~$</span> some command
   Colour the segments with the child spans if the template splits them. */
.prompt {
  white-space: nowrap;
  color: var(--fg-dim);
}

.prompt::before {
  content: "root";
  color: var(--ok);
}

.prompt::after {
  content: "@thugsred";
  color: var(--fg-dim);
}

/* When the template writes the prompt text itself, add .prompt.is-literal
   to suppress the generated segments. */
.prompt.is-literal::before,
.prompt.is-literal::after { content: none; }

.prompt-user { color: var(--ok); }
.prompt-host { color: var(--fg-dim); }
.prompt-path { color: var(--info); }
.prompt-sigil { color: var(--accent); }
.prompt-cmd { color: var(--fg-bright); }

/* The block cursor. Rendered as a styled box rather than a literal "█"
   glyph so it is invisible to assistive tech and to text selection. */
.cursor {
  display: inline-block;
  width: .55em;
  height: 1.05em;
  margin-left: .12em;
  background: var(--accent);
  vertical-align: text-bottom;
  animation: sc-blink var(--blink) steps(2, start) infinite;
}

.cursor-ok { background: var(--ok); }

@keyframes sc-blink { to { visibility: hidden; } }

/* Reduced motion: a steady block, not a blinking one. */
@media (prefers-reduced-motion: reduce) {
  .cursor { animation: none; opacity: .85; }
}

/* ---- .rule: horizontal rule drawn with block characters ---------------- */

/* Markup:  <div class="rule" aria-hidden="true"></div>
   Two stacked repeating gradients give the ▀▄ block-character texture of the
   main site without shipping hundreds of glyphs to a screen reader. */
.rule {
  height: 6px;
  margin: 1.6rem 0;
  border: 0;
  background-image:
    repeating-linear-gradient(to right,
      var(--line) 0 6px, transparent 6px 10px),
    repeating-linear-gradient(to right,
      var(--accent-soft) 0 2px, transparent 2px 14px);
  background-position: 0 0, 0 3px;
  background-size: 100% 3px, 100% 3px;
  background-repeat: repeat-x;
  opacity: .9;
}

.rule-accent {
  background-image:
    repeating-linear-gradient(to right,
      var(--accent) 0 8px, transparent 8px 12px),
    repeating-linear-gradient(to right,
      var(--accent-line) 0 3px, transparent 3px 12px);
}

.rule-tight { margin: .9rem 0; }

/* ---- .ascii-banner ----------------------------------------------------- */

/* Markup contract — the banner is decoration, the <h1> is the heading:

     <div class="ascii-banner-wrap">
       <pre class="ascii-banner" aria-hidden="true">…block art…</pre>
       <h1>Security Check</h1>
     </div>

   Never the only carrier of the page title. It scales with the viewport and
   scrolls horizontally instead of forcing the page wide at 360px. */
.ascii-banner {
  margin: 0 0 .8rem;
  padding: 0;
  color: var(--accent);
  font-family: var(--display);
  font-size: clamp(.3rem, 1.32vw, .78rem);
  line-height: 1.05;
  letter-spacing: 0;
  white-space: pre;
  overflow-x: auto;
  overflow-y: hidden;
  text-shadow: 0 0 18px var(--accent-soft);
  scrollbar-width: none;
}

.ascii-banner::-webkit-scrollbar { height: 0; }

.ascii-banner-wrap { position: relative; }

/* A second, dimmer copy of the art can be stacked behind for depth. */
.ascii-banner-ghost {
  color: var(--fg-faint);
  opacity: .35;
  text-shadow: none;
}

/* ---- .page-head -------------------------------------------------------- */

.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: .6rem 1.2rem;
  margin-bottom: var(--gap);
  padding-bottom: .9rem;
  border-bottom: 1px solid var(--line);
}

.page-head h1 { margin-bottom: .2rem; }

.page-head p {
  margin: 0;
  max-width: 72ch;
  color: var(--fg-dim);
}

.page-head-text { min-width: 0; }

.page-head-aside {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: center;
}

/* A "## " sigil in front of a section heading, terminal-markdown style. */
.section-title {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 2rem 0 .9rem;
  font-size: 1.05rem;
}

.section-title::before {
  content: "##";
  color: var(--accent);
  flex: none;
}

.section-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.section-title:first-child { margin-top: 0; }

/* ---- .breadcrumb ------------------------------------------------------- */

/* <nav aria-label="Breadcrumb"><ol class="breadcrumb">…</ol></nav>
   Rendered as a filesystem path. The separator is generated content so it
   is not announced. */
.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .3rem;
  margin: 0 0 .7rem;
  padding: 0;
  list-style: none;
  font-size: .78rem;
  color: var(--fg-faint);
}

.breadcrumb li {
  display: flex;
  align-items: center;
  gap: .3rem;
  margin: 0;
  min-width: 0;
}

.breadcrumb li + li::before {
  content: "/";
  color: var(--line);
}

.breadcrumb a {
  color: var(--fg-dim);
  border-bottom: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.breadcrumb a:hover,
.breadcrumb a:focus-visible { color: var(--info); }

.breadcrumb [aria-current="page"] { color: var(--fg); }


/* ======================================================== 5. UTILITIES ==== */

.muted { color: var(--fg-dim); }
.faint { color: var(--fg-faint); }
.mono  { font-family: var(--mono); }
.right { text-align: right; }
.left  { text-align: left; }
.center { text-align: center; }
.nowrap { white-space: nowrap; }
.tabular { font-variant-numeric: tabular-nums; }
.break-any { overflow-wrap: anywhere; }

.text-ok   { color: var(--ok); }
.text-warn { color: var(--warn); }
.text-bad  { color: var(--bad); }
.text-info { color: var(--info); }
.text-accent { color: var(--accent); }

/* .truncate — single line, ellipsised. Needs a min-width:0 parent in flex
   contexts, which .row and .card-head already provide. */
.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

/* .stack — vertical rhythm via the owl selector, so it works on any
   element list without per-child margins. */
.stack > * + * { margin-top: var(--stack-gap, 1rem); }
.stack-sm { --stack-gap: .5rem; }
.stack-lg { --stack-gap: 1.75rem; }
.stack > :last-child { margin-bottom: 0; }

/* .row — horizontal flex with sane wrapping defaults. */
.row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--row-gap, .6rem);
  min-width: 0;
}

.row-sm    { --row-gap: .35rem; }
.row-lg    { --row-gap: 1rem; }
.row-tight { flex-wrap: nowrap; }
.row-top   { align-items: flex-start; }
.row-base  { align-items: baseline; }
.row-between { justify-content: space-between; }
.row-end   { justify-content: flex-end; }
.push      { margin-left: auto; }

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

/* Visible again on focus — for skip targets rendered as .sr-only. */
.sr-only-focusable:focus,
.sr-only-focusable:focus-visible {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: normal;
}

.no-print { }
@media print {
  .no-print,
  .site-nav,
  .nav-toggle,
  .theme-toggle { display: none !important; }

  body {
    background: #fff;
    color: #000;
  }
}
