/* rickub app.css — assembled from web/static/css/ (see css_bundle.go). */
@layer vendor, tokens, base, components, pages, overrides;

/* ==== css/tokens.css ==== */
@layer tokens {
/* rickub — plain modern CSS design system.
   Utilitarian, GitHub-like: calm neutral surfaces, border-driven structure, a
   functional BLUE accent for links/focus/selection and a SUCCESS green reserved
   for primary actions + success status. The system sans carries the UI; --mono is
   reserved for code, SHAs, and repo paths. The portal-green survives only in the
   brand mark (--portal-*) so a touch of identity remains. */

:root {
  color-scheme: dark light;

  /* --- surfaces & ink (dark) --- */
  --bg: #0d1117;
  --bg-elev: #161b22;
  --bg-elev-2: #21262d;
  --line: #30363d;
  --line-soft: #21262d;
  --fg: #e6edf3;
  --fg-muted: #7d8590;
  --fg-dim: #6e7681;

  /* --- accent: functional blue (links, focus, selected state) --- */
  --accent: #2f81f7;          /* GitHub link/selection blue */
  --accent-2: #58a6ff;        /* lighter blue for hover/secondary */
  --accent-ink: #ffffff;      /* text on a solid accent fill */
  --accent-soft: color-mix(in oklab, var(--accent) 14%, transparent);
  --accent-line: color-mix(in oklab, var(--accent) 38%, var(--line));
  /* --- success: green, primary actions + success status ONLY --- */
  --success: #3fb950;
  --success-ink: #ffffff;     /* text on a solid success fill */
  --success-soft: color-mix(in oklab, var(--success) 14%, transparent);
  --success-line: color-mix(in oklab, var(--success) 38%, var(--line));
  --ok: #3fb950;              /* alias consumed by legacy var(--ok, …) fallbacks */
  --danger: #f85149;
  --warn: #d29922;            /* running / pending-attention */
  --merge: #a371f7;           /* merged-PR violet */
  /* brand-only portal green — used by the header mark, nowhere in app chrome */
  --portal-1: #7cf6a0;
  --portal-2: #58b8ff;
  --portal-3: #d6ffe6;

  /* Code-surface row hover (the blob view's line-number table). It is NOT
     re-declared per theme: the Chroma theme is github-dark whatever the app
     theme, so the code panel is always a dark ground and a themed tint would
     wash out in light mode. */
  --code-row-hover: rgba(255, 255, 255, .045);
  /* The rest of the code surface, for chrome that sits ON that dark ground
     rather than beside it — the blame view's attribution column. Same rule as
     --code-row-hover: NOT re-declared per theme, because in light mode the
     themed foregrounds would be near-black text on a dark panel. */
  --code-fg-muted: #9198a1;
  --code-fg-dim: #7d8590;
  --code-link: #58a6ff;
  --code-line: rgba(255, 255, 255, .10);
  --code-attr-bg: rgba(255, 255, 255, .022);

  /* --- type scale --- */
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace; /* @kind font */
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; /* @kind font */
  --text-xs: .78rem;
  --text-sm: .88rem;
  --text-base: 1rem;
  --text-lg: 1.12rem;
  --text-xl: 1.4rem;
  --text-2xl: clamp(1.6rem, 3vw, 2.2rem);
  --text-3xl: clamp(2.4rem, 6vw, 4.1rem);

  /* --- spacing rhythm --- */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
  --sp-5: 24px; --sp-6: 32px; --sp-7: 48px; --sp-8: 64px;
  --sp-section: clamp(56px, 8vw, 92px); /* @kind spacing */

  /* --- geometry, elevation, focus --- */
  --radius: 6px;
  --radius-sm: 4px;
  --radius-lg: 12px;
  --pill: 999px;
  --maxw: 1140px;
  --focus: var(--accent);
  /* GitHub-subtle elevation — structure comes from --line borders, not big drops */
  --shadow-sm: 0 1px 0 rgba(1,4,9,.12);
  --shadow: 0 1px 3px rgba(1,4,9,.4), 0 8px 24px -12px rgba(1,4,9,.5);
  --shadow-lg: 0 8px 24px -8px rgba(1,4,9,.55), 0 16px 48px -16px rgba(1,4,9,.6);
  /* --glow is now a plain neutral focus ring — no coloured halo */
  --glow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent);
}

@media (prefers-color-scheme: light) {
  :root {
    --bg: #ffffff; --bg-elev: #f6f8fa; --bg-elev-2: #eaeef2;
    --line: #d0d7de; --line-soft: #d8dee4;
    --fg: #1f2328; --fg-muted: #59636e; --fg-dim: #818b98;
    --accent: #0969da; --accent-2: #218bff; --accent-ink: #ffffff;
    --success: #1a7f37; --success-ink: #ffffff; --ok: #1a7f37;
    --danger: #cf222e; --warn: #9a6700; --merge: #8250df;
    --portal-1: #16b566; --portal-2: #2f86ff; --portal-3: #063;
    --focus: var(--accent);
    --shadow-sm: 0 1px 0 rgba(31,35,40,.04);
    --shadow: 0 1px 3px rgba(31,35,40,.12), 0 8px 24px -12px rgba(31,35,40,.15);
    --shadow-lg: 0 8px 24px -8px rgba(31,35,40,.16), 0 16px 48px -16px rgba(31,35,40,.2);
    --glow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent);
  }
}
/* Explicit theme override (header toggle). Repeats the palettes so a forced
   theme wins regardless of the OS preference. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0d1117; --bg-elev: #161b22; --bg-elev-2: #21262d;
  --line: #30363d; --line-soft: #21262d;
  --fg: #e6edf3; --fg-muted: #7d8590; --fg-dim: #6e7681;
  --accent: #2f81f7; --accent-2: #58a6ff; --accent-ink: #ffffff;
  --success: #3fb950; --success-ink: #ffffff; --ok: #3fb950;
  --danger: #f85149; --warn: #d29922; --merge: #a371f7;
  --portal-1: #7cf6a0; --portal-2: #58b8ff; --portal-3: #d6ffe6;
  --focus: var(--accent);
  --shadow-sm: 0 1px 0 rgba(1,4,9,.12);
  --shadow: 0 1px 3px rgba(1,4,9,.4), 0 8px 24px -12px rgba(1,4,9,.5);
  --shadow-lg: 0 8px 24px -8px rgba(1,4,9,.55), 0 16px 48px -16px rgba(1,4,9,.6);
}
:root[data-theme="light"] {
  color-scheme: light;
  --bg: #ffffff; --bg-elev: #f6f8fa; --bg-elev-2: #eaeef2;
  --line: #d0d7de; --line-soft: #d8dee4;
  --fg: #1f2328; --fg-muted: #59636e; --fg-dim: #818b98;
  --accent: #0969da; --accent-2: #218bff; --accent-ink: #ffffff;
  --success: #1a7f37; --success-ink: #ffffff; --ok: #1a7f37;
  --danger: #cf222e; --warn: #9a6700; --merge: #8250df;
  --portal-1: #16b566; --portal-2: #2f86ff; --portal-3: #063;
  --focus: var(--accent);
  --shadow-sm: 0 1px 0 rgba(31,35,40,.04);
  --shadow: 0 1px 3px rgba(31,35,40,.12), 0 8px 24px -12px rgba(31,35,40,.15);
  --shadow-lg: 0 8px 24px -8px rgba(31,35,40,.16), 0 16px 48px -16px rgba(31,35,40,.2);
}
}

/* ==== css/base.css ==== */
@view-transition { navigation: auto; }

@layer base {
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--text-base);
  background: var(--bg);
  color: var(--fg);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { font-weight: 600; letter-spacing: -.01em; }
.wrap { max-width: var(--maxw); margin-inline: auto; padding-inline: var(--sp-4); }

/* Labels/badges/nav are set in the system sans (utilitarian voice). --mono is
   reserved for code, SHAs, and repo paths — applied per-component where needed. */

/* ---- focus: one visible ring, everywhere ---- */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 6px;
}
:focus:not(:focus-visible) { outline: none; }

/* ---- skip link (a11y) ---- */
.skip-link {
  position: fixed; top: 10px; left: 10px; z-index: 100;
  padding: 10px 16px; border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--fg); border: 1px solid var(--accent-line);
  font-weight: 600; transform: translateY(-160%); transition: transform .18s ease;
}
.skip-link:focus { transform: translateY(0); }

::selection { background: color-mix(in oklab, var(--accent) 30%, transparent); }

/* ---- inline PR-diff comments ---- */
/* Screen-reader-only text: intents are conveyed by a SYMBOL for sighted users and
   by this label for assistive tech, so intent never rides on colour alone. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- view transitions choreography ---- */
::view-transition-old(root) { animation: vt-fade-out .16s ease both; }
::view-transition-new(root) { animation: vt-fade-in .22s ease both; }
@keyframes vt-fade-out { to { opacity: 0; } }
@keyframes vt-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* #14 — checkboxes & radios on-theme. accent-color tints the native control the
   portal green instead of the browser default blue; consistent sizing/alignment
   for the check-field wrapper. */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--accent);
}
}

/* ==== css/components/chrome.css ==== */
@layer components {
/* ---- header ---- */
.site-header {
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: saturate(1.4) blur(12px);
  background: color-mix(in oklab, var(--bg) 74%, transparent);
  border-bottom: 1px solid var(--line-soft);
  view-transition-name: site-header;
}
.site-header .wrap { display: flex; align-items: center; gap: 26px; height: 62px; }
.brand {
  display: flex; align-items: center; gap: 11px;
  font-weight: 800; letter-spacing: -.02em; font-size: 1.14rem;
  view-transition-name: brand;
}
/* brand mark = the portal glyph on a tiny disc. This is the ONE place the
   portal-green identity survives (--portal-*), so its ring stays green even
   though the app accent is now blue. */
.brand .mark {
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  background: radial-gradient(closest-side, var(--bg-elev) 66%, transparent 70%);
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--portal-1) 35%, transparent),
              0 0 16px -3px color-mix(in oklab, var(--portal-1) 55%, transparent);
  transition: box-shadow .2s ease, transform .3s ease;
}
.brand .mark svg { width: 20px; height: 20px; display: block; }
/* Gradient stops + commit dot are coloured from the theme tokens here (external
   stylesheet) so the mark tracks light/dark without any CSP-blocked inline style. */
.brand .mark .g0 { stop-color: var(--portal-3); }
.brand .mark .g1 { stop-color: var(--portal-1); }
.brand .mark .g2 { stop-color: var(--portal-2); }
.brand .mark .dot { fill: var(--portal-1); }
.brand:hover .mark { transform: rotate(28deg); box-shadow: 0 0 0 1px var(--portal-1), 0 0 22px -2px color-mix(in oklab, var(--portal-1) 70%, transparent); }
.nav { display: flex; gap: 20px; margin-left: 6px; }
.nav a { color: var(--fg-muted); font-size: .93rem; font-weight: 500; transition: color .15s; }
.nav a:hover { color: var(--fg); }
.spacer { flex: 1; }

/* theme toggle */
.theme-toggle {
  display: inline-grid; place-items: center;
  width: 36px; height: 36px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--bg-elev); color: var(--fg-muted);
  cursor: pointer; transition: color .15s, border-color .15s, transform .12s;
}
.theme-toggle:hover { color: var(--fg); border-color: var(--accent-line); }
.theme-toggle:active { transform: scale(.94); }
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: inline; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .theme-toggle .icon-moon { display: none; }
  :root:not([data-theme]) .theme-toggle .icon-sun { display: inline; }
}
:root[data-theme="light"] .theme-toggle .icon-moon { display: none; }
:root[data-theme="light"] .theme-toggle .icon-sun { display: inline; }

/* ---- header: signed-in state ---- */
.nav-user { font-weight: 600; font-size: .92rem; color: var(--fg); }
.nav-user:hover { color: var(--accent); }

/* ---- verify-email banner ---- */
.banner {
  display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap;
  padding: 10px 16px; font-size: .9rem;
  background: color-mix(in oklab, var(--accent-2) 16%, var(--bg-elev));
  border-bottom: 1px solid var(--line);
  color: var(--fg);
}
.banner-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--accent-2); font: inherit; font-weight: 600; text-decoration: underline;
}
/* Banner severities — the design's "Error · request failed" and
   "Success · confirmation" cases, plus the storage notice's two levels. Every
   one is the same recipe (16% of a status token over --bg-elev, a 45% rule
   underneath), so they sit together here rather than being re-derived in each
   caller's file: .banner-critical and .banner-error would otherwise have been
   two names for one tint in two files. Colours come from --danger / --success /
   --warn; no hexes (CLAUDE.md — tokens.css is the round-trip file).
   Severity is a colour, never the whole message: pair .banner-error with
   role="alert" and .banner-ok with role="status" so the banner reaches a
   screen reader that cannot see the tint. */
.banner-warn {
  background: color-mix(in oklab, var(--warn) 16%, var(--bg-elev));
  border-bottom-color: color-mix(in oklab, var(--warn) 45%, var(--line));
}
.banner-error,
.banner-critical {
  background: color-mix(in oklab, var(--danger) 16%, var(--bg-elev));
  border-bottom-color: color-mix(in oklab, var(--danger) 45%, var(--line));
}
.banner-ok {
  background: color-mix(in oklab, var(--success) 16%, var(--bg-elev));
  border-bottom-color: color-mix(in oklab, var(--success) 45%, var(--line));
}

/* ---- footer ---- */
.site-footer { border-top: 1px solid var(--line-soft); padding: 40px 0; color: var(--fg-dim); font-size: .88rem; }
.site-footer .wrap { display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.site-footer .foot-links { display: flex; flex-wrap: wrap; gap: 20px; font-family: var(--sans); font-size: .82rem; }
.site-footer .foot-links a { color: var(--fg-dim); }
.site-footer .foot-links a:hover { color: var(--fg); }

/* ---- header nav active state ---- */
.nav a.active { color: var(--fg); }
.nav a.active::after { content: ""; display: block; height: 2px; border-radius: 2px; margin-top: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2)); }

/* #12 — responsive header with a CSS-only hamburger, written MOBILE-FIRST.
   Base (small screens): the hamburger toggle shows and the menu is a full-width
   dropdown, collapsed until the visually-hidden checkbox is checked. At the
   --bp-header breakpoint the menu becomes an inline flex row and the toggle
   hides. Enhancing up (min-width) rather than overriding down (max-width). */
.nav-toggle-cb { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.nav-toggle {
  display: inline-flex; width: 40px; height: 40px; margin-left: auto;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-elev);
  cursor: pointer; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
}
.nav-toggle:hover { border-color: color-mix(in oklab, var(--fg) 28%, var(--line)); }
.nav-toggle-bar { width: 18px; height: 2px; border-radius: 2px; background: var(--fg-muted); transition: background .15s; }
.nav-toggle-cb:focus-visible ~ .nav-toggle { outline: 2px solid var(--focus); outline-offset: 2px; }

/* Base: collapsed dropdown menu. */
.header-menu {
  position: absolute; top: 62px; left: 0; right: 0;
  display: none; flex-direction: column; align-items: stretch; gap: 14px;
  padding: 16px 20px;
  background: color-mix(in oklab, var(--bg-elev) 96%, transparent);
  backdrop-filter: saturate(1.4) blur(12px);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow-lg);
}
.nav-toggle-cb:checked ~ .header-menu { display: flex; }
.header-menu .nav { flex-direction: column; gap: 12px; margin-left: 0; }
.header-menu .spacer { display: none; }
.header-search { display: flex; width: 100%; }
.header-search-input { width: 100%; padding: 7px 12px; font-size: .9rem; }
.header-menu .inline-form,
.header-menu .btn { width: 100%; }
.header-menu .nav-user { padding: 4px 0; }

/* Enhance up: inline header row on wider viewports. */
@media (min-width: 48rem) {
  .nav-toggle { display: none; }
  .header-menu {
    position: static; display: flex; flex-direction: row; align-items: center;
    gap: 26px; flex: 1; min-width: 0;
    padding: 0; background: none; backdrop-filter: none; border-bottom: 0; box-shadow: none;
  }
  .header-menu .nav { flex-direction: row; gap: 20px; margin-left: 6px; }
  .header-menu .spacer { display: block; }
  /* Give the search a real, usable width on the inline header row: a flex-basis
     (not width:auto, which collapses an empty search input to a ~31px sliver) that
     can shrink on tight widths but never below a legible minimum. */
  .header-search { flex: 0 1 240px; width: 240px; min-width: 12rem; max-width: 240px; order: 1; }
  .header-menu .inline-form,
  .header-menu .btn { width: auto; }
  .header-menu .nav-user { padding: 0; }
}

/* ---- notifications: header bell + inbox + preferences ---- */
.notif-bell {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--radius-sm); color: var(--fg-muted);
  border: 1px solid transparent; transition: background .12s, color .12s, border-color .12s;
}
.notif-bell:hover { background: var(--bg-elev); color: var(--fg); }
.notif-bell.active { color: var(--fg); border-color: var(--line); background: var(--bg-elev); }
/* DELIBERATE DIVERGENCE from the design system, whose rule is
   `font-size: 1rem; line-height: 1; filter: grayscale(.2)` — all three written for
   the 🔔 emoji this bell used to be (effbf23) and stopped being in 52cf4fa, which
   swapped it for a monochrome currentColor line-icon to match the header's other
   glyphs. With an <svg> child sized in px below, font-size sets nothing;
   grayscale(.2) would desaturate --fg-muted/--fg, i.e. re-mute an icon that is
   already the header's own colour, and make this span a filter containing block for
   it; and line-height: 0 (not 1) is the SVG-in-a-span idiom — the defensive value
   that stops this inline-level box contributing leading to a line, whereas 1 is
   what an emoji glyph wants. Adopting the canvas values here would be styling an
   element we do not render. docs/design-sync.md, 2026-08-29. */
.notif-bell-icon { display: inline-flex; line-height: 0; }
.notif-bell-icon svg { width: 18px; height: 18px; }
.notif-badge {
  position: absolute; top: -3px; right: -4px; min-width: 17px; height: 17px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  font: 700 .64rem var(--mono); line-height: 1; border-radius: var(--pill);
  background: var(--accent); color: var(--accent-ink); border: 1px solid var(--bg);
}

/* Header nav is set in sans (the reading voice), confirming the inherited body
   font explicitly. A count-badge that appears inside the header nav overrides the
   mono utility-label rule above so it matches the surrounding nav text; the mono
   voice is kept for count-badges elsewhere (repo section nav, MR tabs). */
.nav a, .nav-user, .nav .count-badge { font-family: var(--sans); }
}

/* ==== css/components/buttons.css ==== */
@layer components {
/* ---- buttons (GitHub-style: subtle default, solid-success primary) ---- */
.btn {
  --b: var(--line);
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 7px 14px; min-height: 32px; border-radius: var(--radius);
  font-family: var(--sans); font-weight: 600; font-size: .875rem; line-height: 1.25;
  cursor: pointer; white-space: nowrap;
  border: 1px solid var(--b); background: var(--bg-elev-2); color: var(--fg);
  transition: background .12s ease, border-color .12s ease, box-shadow .12s ease;
}
.btn:hover { background: color-mix(in oklab, var(--fg) 8%, var(--bg-elev-2)); border-color: color-mix(in oklab, var(--fg) 28%, var(--line)); }
.btn:active { background: color-mix(in oklab, var(--fg) 12%, var(--bg-elev-2)); }
.btn.primary {
  background: var(--success); color: var(--success-ink);
  border-color: color-mix(in oklab, #000 14%, var(--success));
}
.btn.primary:hover { background: color-mix(in oklab, #000 8%, var(--success)); border-color: color-mix(in oklab, #000 20%, var(--success)); box-shadow: none; }
.btn.primary:active { background: color-mix(in oklab, #000 14%, var(--success)); }
.btn.ghost { background: transparent; border-color: transparent; }
.btn.ghost:hover { background: var(--bg-elev-2); border-color: var(--line); }
.btn.ghost.danger { color: var(--danger); }
.btn.ghost.danger:hover { border-color: color-mix(in oklab, var(--danger) 50%, var(--line)); background: color-mix(in oklab, var(--danger) 8%, transparent); }
.btn.full { width: 100%; justify-content: center; }
.btn.disabled { opacity: .5; pointer-events: none; }

/* Small button modifier reused by the inline forms. */
.btn.sm { padding: 4px 10px; font-size: .78rem; }

/* Icon-only modifier — a square 32x32 button carrying a single glyph and no
   text (the releases-list delete affordance). It is a MODIFIER on .btn, never
   a button of its own: the colour still comes from .ghost / .ghost.danger, so
   there is exactly one place that decides what "destructive" looks like. Its
   accessible name must come from aria-label, since the glyph is aria-hidden. */
.icon-btn { width: 32px; height: 32px; padding: 0; font-size: .95rem; line-height: 1; }

.btn-sm { padding: 4px 12px; font-size: .82rem; }
}

/* ==== css/components/labels.css ==== */
@layer components {
/* ---- kicker (mono section label) + eyebrow ---- */
/* nowrap: the ninth rule dropped by the same bad sync as the eight fixed in
   1765813 (chips, badges, nav counts, rule flags). An eyebrow is a pill —
   "pricing · EUR · no lock-in" breaking after a middot leaves the border
   drawn round two half-lines. Same reason, same fix, one file further out. */
.eyebrow {
  display: inline-flex; align-items: center; gap: 8px; white-space: nowrap;
  font-size: .74rem; font-weight: 600; letter-spacing: .04em; color: var(--accent);
  border: 1px solid var(--accent-line);
  padding: 5px 12px; border-radius: var(--pill); margin-bottom: 22px;
  background: var(--accent-soft);
}
.kicker {
  display: block; font-size: .74rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .16em; color: var(--accent-2);
  margin-bottom: 12px;
}

/* nowrap per the design system's components/tabs.css — the eighth of the eight
   pills that lost it in one bad sync; the argument is written out once over
   .badge in pages/repo.css. A count is short, so this one is insurance rather
   than a visible fix: it keeps the badge a circle when a tab label beside it is
   what runs out of room. */
.count-badge {
  display: inline-block; min-width: 20px; text-align: center; padding: 0 6px;
  white-space: nowrap;
  font-size: .72rem; line-height: 18px; border-radius: var(--pill);
  background: var(--bg-elev-2); border: 1px solid var(--line); color: var(--fg-muted);
}
}

/* ==== css/components/list-filters.css ==== */
@layer components {
/* The head row itself, and its action cluster. Both are rendered by the shared
   `listPageHead` (components/pulls.templ) and hand-written on the commits,
   releases, tags and labels pages — seven surfaces, none of which is the pull
   request page whose stylesheet they lived in. In `pages` they also outranked
   the `.list-filters` rules they contain, which is the arrangement CLAUDE.md
   names: a "small tweak" to pages/pr.css restyles the repo home's empty-repo
   nudge. Same move, same reason, as `.pr-tabs` below. */
.pr-list-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 14px; flex-wrap: wrap; }
/* Action cluster: New issue / Labels / Milestones on a list head, the quickstart
   link in the empty-repo nudge, Compare + the ref switcher on the commit log. */
.pr-list-actions { display: inline-flex; gap: 8px; align-items: center; }
/* `.pr-list-head > .settings-subhead` deliberately did NOT come with them, and
   stays in pages/pr.css. It overrides `.settings-subhead`, which is defined in
   pages/settings.css — and `@layer` order is absolute: a `components` rule
   cannot beat a `pages` one at any specificity, so moving that override here
   would silently stop it applying and every list head would grow its section
   divider back. An override lives in the layer of the rule it overrides. The
   root cause is that .settings-subhead is itself shared chrome in a page file
   (issue #160). */

/* Shared filter row for the issue and merge-request lists (components/pulls.templ,
   `listFilters`): state tabs on the left, title search + sort select pushed to
   the right. It sits INSIDE .pr-list-head beside that page's action buttons, so
   it grows to fill whatever the buttons leave and wraps onto its own line before
   it squeezes them. */
.list-filters {
  display: flex; align-items: center; gap: 16px;
  flex: 1 1 22rem; min-width: 0; flex-wrap: wrap;
}

/* The tab row itself: the state filters rendered by `listFilterTabs`, and the
   Releases | Tags sub-nav on the two repo ref pages (`refSectionNav`). It lived
   in pages/pr.css while the component that renders it lived here, which is the
   split that lets a page-layer rule and its component drift apart; three pages
   now depend on it, so it belongs in the component layer with .list-filters. */
.pr-tabs { display: flex; gap: 16px; }
.pr-tab { color: var(--fg-muted); font-size: .92rem; font-weight: 500; }
.pr-tab.active, .pr-tab:hover { color: var(--fg); }

/* margin-left:auto (not justify-content on the parent) keeps the tabs hard left
   and the controls hard right even when only one of them is present. */
.list-search { display: flex; align-items: center; gap: 8px; margin-left: auto; }

/* A search field wants to be generous but must never be the reason the header
   wraps on a laptop, hence a clamp rather than a fixed width. The UA's own
   clear button ("x" in Safari/Chrome for type=search) is left alone: it is the
   fastest way back to the unfiltered list. */
.list-search-input { width: clamp(9rem, 22vw, 15rem); }

/* The sort select is the narrowest control in the row; "Most commented" is the
   widest label it has to hold without truncating. */
.list-search .c-select select { min-width: 10.5rem; }

/* Below the header's wrap point the controls own the full line and the input
   takes the slack, rather than leaving a ragged gap beside the select. */
@media (max-width: 34rem) {
  .list-search { width: 100%; margin-left: 0; }
  .list-search-input { flex: 1 1 auto; width: auto; }
}
}

/* ==== css/components/forms.css ==== */
@layer components {
.inline-form { display: inline; margin: 0; }

.form { display: grid; gap: 16px; }
.field { display: grid; gap: 6px; }
.field-label { font-size: .85rem; font-weight: 600; color: var(--fg-muted); }
.field-input {
  padding: 9px 12px; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--bg); color: var(--fg);
  font: inherit; font-size: .95rem; transition: border-color .15s, box-shadow .15s;
}
.field-input::placeholder { color: var(--fg-dim); }
.field-input:hover { border-color: color-mix(in oklab, var(--fg) 18%, var(--line)); }
.field-input:focus-visible {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent);
}
.field-input:disabled { opacity: .6; cursor: not-allowed; }
/* Every native <select> in a form field, drawn once. There are two select
   shapes in this design system and they are NOT interchangeable:

     - `<select class="field-input">` — a select inside a form field (26 of the
       29 in the app). It is a bare element with our own chevron painted on as
       a background image, so it needs no wrapper.
     - `.c-select > select` (components/console.css) — a standalone select in a
       toolbar (the sort control, the reviews picker). Its chevron is an
       `::after` on the WRAPPER, so the class only works on a container with a
       select inside it; putting `.c-select` on a `<select>` itself styles
       nothing at all.

   The two are the same look reached two ways, which is why this rule sat in
   pages/settings.css captioned "matches .c-select": a component's own base
   style, in one page's file, in the layer that beats components. Every page in
   the app renders `select.field-input`; it belongs here beside .field-input.
   Guarded by TestSelectChevronLivesWithTheFieldItDecorates. */
select.field-input { appearance: none; cursor: pointer; padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='7' viewBox='0 0 10 7'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238b94a6' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-position: calc(100% - 13px) center; background-size: 10px 7px; background-repeat: no-repeat; }
.field-input.has-error { border-color: var(--danger); }
.field-input.has-error:focus-visible { box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 30%, transparent); }
.field-hint { font-size: .78rem; color: var(--fg-dim); }
/* A file input renders its own OS-drawn button, so the text-input padding makes
   it sit too tall and the fixed font-size fights the native control. Trim both
   and let the browser draw its picker inside our border. */
.field-input.file-input { padding: 7px 10px; font-size: .88rem; cursor: pointer; }
.field-input.file-input::file-selector-button {
  font: inherit; margin-right: 10px; padding: 5px 11px; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-elev-2); color: var(--fg);
}
.field-input.file-input::file-selector-button:hover { border-color: var(--accent-line); }
.field-error { font-size: .8rem; color: var(--danger); font-weight: 500; }
.form-error {
  padding: 11px 14px; border-radius: var(--radius-sm); font-size: .9rem;
  background: color-mix(in oklab, var(--danger) 14%, var(--bg-elev));
  border: 1px solid color-mix(in oklab, var(--danger) 45%, var(--line));
  color: var(--fg); border-left: 3px solid var(--danger);
}
.form-ok {
  padding: 11px 14px; border-radius: var(--radius-sm); font-size: .9rem;
  background: color-mix(in oklab, var(--success) 14%, var(--bg-elev));
  border: 1px solid color-mix(in oklab, var(--success) 45%, var(--line));
  color: var(--fg); border-left: 3px solid var(--success);
}
.form-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
}

/* ==== css/components/settings-nav.css ==== */
@layer components {
/* ---- settings sidebar (settingsNavShell) ----
   The vertical section list shared by the three settings families: account
   (/settings/*), organization (/{org}/settings/*) and repository
   (/{owner}/{repo}/settings/*). Its markup is ONE component, so its styling is
   one file in the layer all three share — a copy in pages/ would beat this one
   whatever its specificity, which is how a tweak on one family silently
   restyles the other two. Guarded by TestSettingsNavChromeLivesInComponentLayer.

   The column width itself is NOT here: that is the page grid
   (`.settings` in pages/settings.css), which is a layout decision per family. */
.settings-nav-title { margin: 0 0 12px; font-size: .74rem; text-transform: uppercase; letter-spacing: .12em; color: var(--fg-dim); }
.settings-nav nav { display: flex; flex-direction: column; gap: 2px; }
.settings-nav-link {
  padding: 8px 12px; border-radius: var(--radius-sm); color: var(--fg-muted);
  font-size: .93rem; border: 1px solid transparent; transition: background .12s, color .12s;
}
.settings-nav-link:hover { background: var(--bg-elev); color: var(--fg); }
.settings-nav-link.active { background: var(--bg-elev); color: var(--fg); border-color: var(--line); font-weight: 600; box-shadow: inset 3px 0 0 var(--accent); }
/* A plan-gated section (SSO, Audit log) stays in the list and links to billing
   rather than vanishing — dimmed, with the lock pushed to the right edge. */
.settings-nav-link.locked { color: var(--fg-dim); display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.settings-nav-link.locked:hover { color: var(--fg-muted); }
.nav-lock { font-size: .8em; opacity: .7; }
}

/* ==== css/components/console.css ==== */
@layer components {
/* ---- Console (design-system Direction 01) form controls ----
   A crisp-chevron select and the subject picker. In the narrow reviews sidebar
   the picker stacks vertically — segmented User/Team toggle, typeahead input,
   full-width Add — so it never overflows the column. The radios post the same
   subject_type field the old <select> did. */
.c-select { position: relative; display: grid; }
.c-select select {
  appearance: none; cursor: pointer; width: 100%; font: inherit; font-size: .92rem; color: var(--fg);
  padding: 9px 38px 9px 12px; border-radius: var(--radius-sm);
  background: var(--bg-elev-2); border: 1px solid var(--line);
  transition: border-color .14s, box-shadow .14s;
}
.c-select select:hover { border-color: var(--accent-line); }
.c-select select:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.c-select::after {
  content: ""; position: absolute; right: 14px; top: 50%; width: 7px; height: 7px;
  border-right: 2px solid var(--fg-muted); border-bottom: 2px solid var(--fg-muted);
  transform: translateY(-65%) rotate(45deg); pointer-events: none; transition: border-color .14s;
}
.c-select:hover::after { border-color: var(--accent); }

/* segmented toggle: self-contained (its own border) so it stands alone in a
   stacked form; .seg-full makes the two options split the width 50/50. */
.seg { display: flex; border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; background: var(--bg); }
.seg-full { display: grid; grid-template-columns: 1fr 1fr; }
.seg input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.seg label { display: grid; place-items: center; padding: 8px 12px; font-size: .82rem; font-weight: 600; color: var(--fg-dim); cursor: pointer; transition: color .12s, background .12s; }
.seg label:first-of-type { border-right: 1px solid var(--line-soft); }
.seg input:checked + label { color: var(--accent-ink); background: var(--accent); }
.seg input:focus-visible + label { box-shadow: inset 0 0 0 2px var(--focus); }
.subject-add { gap: 8px; }
}

/* ==== css/components/markdown-body.css ==== */
@layer components {
/* ---- rendered markdown (.markdown-body) ----
   The typography of any block of markdown rickub has rendered to HTML. NINE
   surfaces put this class on an element: the README panel and the rendered
   view of a markdown blob (explorer.templ), an issue's or merge request's
   description and every comment on either (markdown_editor.templ, via
   MarkdownEditorView.RenderedClass), both live preview panes, a release's
   notes (repo_features.templ), Athena's review summary (pull_athena.templ) and
   a CI step's summary (actions.templ).

   It lived in pages/repo.css, where the README made it look like repo-home
   chrome, and that is not a filing quibble: `pages` beats `components` in the
   @layer order whatever the specificity, so that file's `padding: 24px 28px`
   silently outranked the padding of every COMPONENT that renders a markdown
   body. The comment bubble asks for `14px 16px` and got 24/28 — a gap under
   every description and every reply in the app — and the editor's preview pane
   asks for --sp-3 and got the same. Both are correct again by moving the rules
   rather than by fighting them, which is the .pr-tabs / .pr-comment-* lesson
   for the third time. Pinned by TestMarkdownBodyLivesInComponentLayer.

   It sits FIRST among the components that decorate a markdown body, so any of
   them (markdown-editor.css's .mde-preview, comment-thread.css's
   .pr-comment-body) can tune it from the same layer, and the page files that
   size a particular markdown surface (.md-view-rendered, .release-summary)
   keep winning from `pages` as they always did. */
.markdown-body { padding: 24px 28px; color: var(--fg); }
.markdown-body h1, .markdown-body h2, .markdown-body h3 { letter-spacing: -.02em; margin: 1.4em 0 .5em; }
.markdown-body h1 { font-size: 1.6rem; border-bottom: 1px solid var(--line-soft); padding-bottom: .3em; }
.markdown-body h2 { font-size: 1.3rem; border-bottom: 1px solid var(--line-soft); padding-bottom: .3em; }
.markdown-body h1:first-child, .markdown-body h2:first-child { margin-top: 0; }
.markdown-body p { margin: 0 0 1em; }
/* The LAST block of a body has nothing to separate itself from, and the box
   around it already has padding. Without this a bubble's 14px foot became 14px
   plus a 1em paragraph margin — the other half of the gap the pre-wrap removal
   closed, and the same trailing slack under every README and release note. */
.markdown-body > :last-child { margin-bottom: 0; }
.markdown-body a { color: var(--accent-2); text-decoration: underline; text-underline-offset: 2px; }
.markdown-body code { font-family: var(--mono); font-size: .86em; background: var(--bg-elev-2); padding: .15em .4em; border-radius: 5px; }
.markdown-body pre { background: var(--bg-elev-2); border: 1px solid var(--line-soft); border-radius: var(--radius-sm); padding: 14px 16px; overflow-x: auto; }
.markdown-body pre code { background: none; padding: 0; }
.markdown-body ul, .markdown-body ol { margin: 0 0 1em; padding-left: 1.6em; }
.markdown-body blockquote { margin: 0 0 1em; padding-left: 14px; border-left: 3px solid var(--accent-line); color: var(--fg-muted); }
.markdown-body table { border-collapse: collapse; margin: 0 0 1em; }
.markdown-body th, .markdown-body td { border: 1px solid var(--line); padding: 6px 12px; }
.markdown-body img { max-width: 100%; }
}

/* ==== css/components/markdown-editor.css ==== */
@layer components {
/* ---- Inline markdown editor (components/markdown_editor.templ) ----
   One component, four call sites: the issue description, the merge-request
   description, and every comment on either. All values come from tokens.css —
   nothing here hardcodes a colour or a step of the type/spacing scale.

   Two states, no JS required for either:
     - collapsed  — the rendered markdown plus an "Edit …" <summary>
     - open       — the same markdown plus a <form> with a <textarea>
   The island (islands/markdown-editor.ts) additionally un-hides the toolbar,
   the Write/Preview tabs, the attach control and Cancel, and adds .mde-editing
   to the root so the rendered copy gets out of the way while editing. Without
   JS those affordances stay [hidden] and the plain form still works. */

.mde { display: block; }
.mde-view { position: relative; }

/* The UA's `[hidden] { display: none }` is beaten by ANY author `display` on the
   element (the same trap pages/actions.css documents for the CI banner) — and the
   toolbar, the tab strip and the "edited" chip below all set one. Without this
   rule a JS-less page would show every JS-only affordance. Specificity (0,2,0)
   outranks the single-class rules below, so it wins regardless of file order, and
   it stops applying the moment the island removes the attribute. */
.mde [hidden] { display: none; }

/* While the island has the editor open, the rendered copy is hidden — the
   textarea takes its place, which is what "in place" means here. */
.mde-editing > .mde-view { display: none; }

.mde-edited {
  display: inline-block; margin-top: var(--sp-1);
  font: var(--text-xs) var(--mono); color: var(--fg-dim);
}
.mde-edited::before { content: "("; }
.mde-edited::after { content: ")"; }

/* --- the disclosure ------------------------------------------------------- */
.mde-shell { display: block; }
.mde-toggle {
  display: inline-block; cursor: pointer; list-style: none;
  padding: var(--sp-1) var(--sp-2); margin-bottom: var(--sp-2);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-elev); color: var(--fg-muted);
  font-size: var(--text-xs); font-weight: 600;
  transition: color .12s, border-color .12s, background .12s;
}
.mde-toggle::-webkit-details-marker { display: none; }
.mde-toggle:hover { color: var(--fg); border-color: var(--accent-line); background: var(--bg-elev-2); }
.mde-toggle:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--glow); }
/* The island drives open/closed itself and hides the summary once mounted. */
.mde-editing .mde-toggle { display: none; }

.mde-form {
  display: flex; flex-direction: column; gap: var(--sp-2);
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-elev); padding: var(--sp-3);
}

/* --- Write / Preview tabs -------------------------------------------------- */
.mde-tabs { display: flex; gap: var(--sp-1); border-bottom: 1px solid var(--line-soft); }
.mde-tab {
  appearance: none; cursor: pointer; font: inherit; font-size: var(--text-xs); font-weight: 600;
  padding: var(--sp-2) var(--sp-3); border: 0; border-bottom: 2px solid transparent;
  background: transparent; color: var(--fg-muted);
  transition: color .12s, border-color .12s;
}
.mde-tab:hover { color: var(--fg); }
.mde-tab:focus-visible { outline: none; color: var(--fg); box-shadow: var(--glow); border-radius: var(--radius-sm); }
.mde-tab.active { color: var(--fg); border-bottom-color: var(--accent); }

/* --- toolbar --------------------------------------------------------------- */
.mde-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1); }
.mde-cmd {
  appearance: none; cursor: pointer; min-width: 30px; height: 30px;
  padding: 0 var(--sp-2); border: 1px solid transparent; border-radius: var(--radius-sm);
  background: transparent; color: var(--fg-muted);
  font: 600 var(--text-xs) var(--mono);
  transition: color .12s, background .12s, border-color .12s;
}
.mde-cmd:hover { color: var(--fg); background: var(--bg-elev-2); border-color: var(--line); }
.mde-cmd:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--glow); }

.mde-attach { margin-inline-start: auto; cursor: pointer; }
.mde-attach-label {
  display: inline-block; padding: 0 var(--sp-2); line-height: 30px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-elev-2); color: var(--fg-muted);
  font-size: var(--text-xs); font-weight: 600;
  transition: color .12s, border-color .12s;
}
.mde-attach:hover .mde-attach-label { color: var(--fg); border-color: var(--accent-line); }
/* The real <input type=file> is visually hidden but still focusable/labelled. */
.mde-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.mde-file:focus-visible + .mde-attach-label,
.mde-attach:focus-within .mde-attach-label { border-color: var(--accent); box-shadow: var(--glow); }

/* --- textarea + preview ----------------------------------------------------- */
.mde-textarea {
  min-height: 140px; resize: vertical;
  font-family: var(--mono); font-size: var(--text-sm); line-height: 1.55;
}
/* Drag-over feedback while an image is hovering the editor. */
.mde-dropping .mde-textarea { border-color: var(--accent); box-shadow: var(--glow); }

.mde-preview {
  min-height: 140px; padding: var(--sp-3);
  border: 1px dashed var(--line); border-radius: var(--radius-sm);
  background: var(--bg);
}
.mde-preview img, .mde-rendered img { max-width: 100%; height: auto; border-radius: var(--radius-sm); }

.mde-status { margin: 0; min-height: 1.2em; font-size: var(--text-xs); color: var(--fg-muted); }
.mde-status.error { color: var(--danger); }

.mde-actions { display: flex; gap: var(--sp-2); align-items: center; }

/* --- the composer (MarkdownComposer) ---------------------------------------
   The authoring half on its own, inside a form the page owns. It has no
   .mde-form of its own — the caller's form is the box — so the stacking that
   .mde-form did for the editor is done here instead. Everything else (toolbar,
   tabs, textarea, preview, status) is the same markup and the same rules. */
.mde-composer { display: flex; flex-direction: column; gap: var(--sp-2); }
}

/* ==== css/components/comment-thread.css ==== */
@layer components {
/* ---- discussion threads (components/comment_thread.templ) ----
   One component renders this on BOTH detail pages — an issue's thread and a
   merge request's — so its CSS is a component, not a page's.

   NAMES COME FROM THE DESIGN SYSTEM. These were `.pr-comment*` until the design
   system promoted the artboards' `.discussion-*` rules out of the Pages
   project's app.css and into css/pages/pr.css. Two names for one component is
   how a design system and its app stop being comparable at all: the property
   diff can only check a selector both sides define, so a rename reads as
   "clean" forever while the two drift underneath. Renamed to match, so the
   check has something to bite on.

   Each row is avatar + bubble; the bubble's ::before tail is CLIPPED by
   `overflow: hidden` — the design draws no tail, and a previous commit here
   removed the clip to "fix" the tail into existence, which was a divergence,
   not a fix. */

.discussion-thread { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-direction: column; gap: 18px; }
.discussion-item { display: flex; gap: 12px; align-items: flex-start; }
/* The design system's thread avatar is a 40px CIRCLE, and self-contained — not
   the 26px rounded square `.avatar.small` used in lists. It carries no `.avatar`
   class on purpose: `.avatar` lives in pages/dashboard.css, and a `pages` rule
   beats a `components` one whatever the specificity, so `.discussion-avatar`
   could never have resized it from here (the same layer inversion as #166).
   Standing alone, it needs its own fill — the accent gradient `.avatar` uses, so
   one person's disc is the same colour wherever it appears. The design system's
   `-morty`/`-rick` variants are artboard mock data and have no app equivalent. */
.discussion-avatar {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: .92rem; color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .14);
}
.discussion-bubble {
  position: relative; flex: 1; min-width: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: 10px; background: var(--bg-elev);
}
/* NO ::before TAIL — and that is a rule divergence with zero rendering
   divergence. The design system declares one at left:-7px and then clips it with
   the `overflow: hidden` above, so it paints nothing there either. Carrying an
   unreachable rule is precisely how 2cd9192 happened: someone found a tail that
   never rendered, removed the clip to "fix" it, and shipped a spike no artboard
   draws. TestCommentBubbleRendersNoTail holds both halves. */
.discussion-bubble-head {
  display: flex; align-items: baseline; gap: 8px;
  padding: 9px 14px; background: var(--bg-elev-2);
  border-bottom: 1px solid var(--line-soft);
  /* 9px = the bubble's 10px radius minus its 1px border. Redundant while the
     parent clips, and deliberately kept: it is what makes the head correct
     independently of the clip. */
  border-radius: 9px 9px 0 0;
  font-size: .84rem; color: var(--fg-muted);
}
.discussion-bubble-head strong { color: var(--fg); font-weight: 700; }
.discussion-bubble-body { padding: 14px 16px; }

/* The composer is the thread's last row: the viewer's avatar beside a form that
   takes the remaining width. It is a <div>, not an <li> — the list is the record
   of what WAS said, and an empty form is not a comment. The form is a DIRECT
   child (no bubble): the design system styles `.discussion-composer > .form`. */
.discussion-composer { margin: 0 0 16px; }
.discussion-composer > .form { flex: 1; min-width: 0; }

/* The edit control lives in the bubble's top-right, over the head strip: a
   pencil, not a labelled button under the prose. Absolute against the bubble
   (which is position:relative and clips to its own radius), so it sits in the
   head's corner without being a flex child of it — the head's baseline
   alignment would otherwise drag it onto the text's baseline.
   .mde-shell is display:block and would reserve a row of its own; here it is
   the positioning context's child and takes no layout space at all. */
.discussion-bubble .mde-shell { position: static; }
.discussion-bubble .mde-toggle {
  position: absolute; top: 5px; right: 6px; z-index: 1;
  margin: 0; padding: 2px 6px; line-height: 1;
  border-color: transparent; background: transparent;
  opacity: .55; transition: opacity .12s, border-color .12s, background .12s;
}
.discussion-bubble .mde-toggle:hover,
.discussion-bubble .mde-toggle:focus-visible { opacity: 1; background: var(--bg-elev); border-color: var(--line); }
/* Touch has no hover, so the affordance must be visible from the start. */
@media (hover: none) { .discussion-bubble .mde-toggle { opacity: 1; } }
.mde-toggle-icon { font-size: .82rem; }
/* Icon-only INSIDE a bubble; every other editor keeps its words. */
.discussion-bubble .mde-toggle-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.mde-toggle-icon + .mde-toggle-label { margin-left: 6px; }

/* A retracted comment leaves a tombstone in the thread rather than vanishing
   between the replies that answer it (issue #13). App-local: the design system
   has no concept of a deleted comment. */
.comment-tombstone { color: var(--fg-dim); font-style: italic; font-size: .9rem; margin: 6px 0 0; }
.comment-delete { margin-left: auto; }
}

/* ==== css/components/progress.css ==== */
@layer components {
/* #22 — global loading affordance. htmx toggles .htmx-request on an element with
   hx-indicator; the spinner shows only while a request is in flight.

   Two class names, ONE rule set. `.hx-indicator` is our own markup convention;
   `.htmx-indicator` is htmx's built-in default (`htmx.config.indicatorClass`),
   which htmx normally styles itself by injecting a <style> element into <head>
   at init (insertIndicatorStyles). That injection is an INLINE stylesheet, and
   our CSP (`style-src 'self'`, no 'unsafe-inline', no nonce — web/middleware.go,
   App.secure) blocks it, logging a violation on every page load. So we set
   `"includeIndicatorStyles":false` in components.HTMXConfig (web/components/htmx.go)
   and serve the equivalent rules from this same-origin bundle instead.

   They live HERE, in one selector list, rather than in a second CSS file: a
   separate copy of "opacity 0 → 1 while a request is in flight" is exactly the
   kind of duplicate that drifts when someone retunes the fade (CLAUDE.md, DRY
   rung 1). htmx's own injected rules used `opacity 200ms ease-in`; folding them
   into this rule set adopts our design system's timing instead of importing
   htmx's arbitrary default — same behaviour, one place to change it.
   TestHTMXIndicatorStylesShipped pins the pairing: disabling the injection
   without shipping these rules (or vice versa) fails the build. */
.hx-indicator,
.htmx-indicator { opacity: 0; transition: opacity .12s ease; }
.htmx-request .hx-indicator,
.htmx-request.hx-indicator,
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { opacity: 1; }
.top-progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 100%;
  transform: scaleX(0); transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  opacity: 0; z-index: 100; pointer-events: none;
}
body.htmx-request .top-progress {
  opacity: 1; animation: top-progress-run 1.4s ease-out infinite;
}
@keyframes top-progress-run {
  0% { transform: scaleX(0); }
  60% { transform: scaleX(.75); }
  100% { transform: scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
  body.htmx-request .top-progress { animation: none; transform: scaleX(1); }
}
}

/* ==== css/components/skeleton.css ==== */
@layer components {
/* Loading skeletons — the design's States catalogue ("Loading · list",
   "Loading · diff"). Before this file every htmx-swapped region on the site
   showed NOTHING while it fetched, so a slow request was indistinguishable
   from a dead one.

   A skeleton must never outlive its content. These are wired as htmx
   indicators (`class="sk hx-indicator"` on the block, `hx-indicator` on the
   element htmx swaps), and htmx removes the request class on every terminal
   path — xhr.onload for ANY status, onerror, ontimeout, onabort — so a 500
   hides the skeleton instead of leaving the page pretending to still load.
   `.hx-indicator` opacity is progress.css's job; the display switch is here,
   because a skeleton is a layout-sized block: fading it out at rest would
   leave a four-row hole in the middle of the page. Without `.hx-indicator`
   the block is simply always visible — that is how the style guide shows it.

   Geometry follows the design: 10px bars, --pill radius, --bg-elev-2 fill,
   1.4s shimmer. The shimmer is a moving highlight, exactly the sort of
   repeating motion that provokes vestibular symptoms, so it is DROPPED (not
   slowed) under prefers-reduced-motion — the flat bars still read as
   placeholders. */
.sk { display: grid; gap: 10px; }
.sk.hx-indicator { display: none; }
.htmx-request .sk.hx-indicator,
.htmx-request.sk.hx-indicator { display: grid; }

.sk-row { display: flex; align-items: center; gap: 12px; }

.sk-bar {
  flex: 1 1 auto; min-width: 0;
  height: 10px; border-radius: var(--pill);
  background: var(--bg-elev-2);
  position: relative; overflow: hidden;
}
/* Ragged line ends: a placeholder whose every row is the same full width reads
   as a table, not as prose waiting to arrive. */
.sk-bar-short { flex: 0 0 22%; }
.sk-bar-mid { flex: 0 0 44%; }

.sk-dot {
  flex: 0 0 auto;
  width: 26px; height: 26px; border-radius: var(--pill);
  background: var(--bg-elev-2);
  position: relative; overflow: hidden;
}

.sk-bar::after,
.sk-dot::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(
    90deg,
    transparent,
    color-mix(in oklab, var(--fg) 12%, transparent),
    transparent);
  transform: translateX(-100%);
  animation: sk-shimmer 1.4s ease-in-out infinite;
}
@keyframes sk-shimmer { to { transform: translateX(100%); } }

@media (prefers-reduced-motion: reduce) {
  .sk-bar::after,
  .sk-dot::after { display: none; }
}
}

/* ==== css/components/layout.css ==== */
@layer components {
/* ============================================================================
   Layout primitives — composable, mobile-first building blocks.
   Prefer these over bespoke @media grids: they reflow intrinsically (or by
   CONTAINER width) so a component looks right in any slot without new
   breakpoints. Each self-guards on empty content and takes its knobs from
   custom properties so callers vary only what genuinely differs.
   ============================================================================ */

/* .stack — vertical rhythm; children separated by --stack-gap (default sp-4). */
.stack { display: flex; flex-direction: column; }
.stack > * + * { margin-top: var(--stack-gap, var(--sp-4)); }

/* .cluster — horizontal group that WRAPS (button rows, chip rows, meta rows). */
.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--cluster-gap, var(--sp-3)); }

/* .grid-auto — responsive gallery with no media queries: as many columns as fit
   at >= --grid-min wide, collapsing to one on narrow screens. */
.grid-auto {
  display: grid; gap: var(--grid-gap, var(--sp-4));
  grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-min, 240px), 100%), 1fr));
}

/* .sidebar — main content + a fixed-width side column. Mobile-first: one column;
   the side column appears at >= 57rem. --sidebar-w sets its width; .sidebar.start
   places the side column first instead of last. Children never overflow (min-0). */
.sidebar { display: grid; gap: var(--sidebar-gap, var(--sp-5)); grid-template-columns: 1fr; align-items: start; }
.sidebar > * { min-width: 0; }
@media (min-width: 57rem) {
  .sidebar { grid-template-columns: minmax(0, 1fr) var(--sidebar-w, 300px); }
  .sidebar.start { grid-template-columns: var(--sidebar-w, 260px) minmax(0, 1fr); }
}

/* .center — horizontally centred column capped at --center-max (default maxw). */
.center { box-sizing: content-box; max-width: var(--center-max, var(--maxw)); margin-inline: auto; padding-inline: var(--sp-4); }
}

/* ==== css/components/empty-states.css ==== */
@layer components {
/* Empty states, one file, named after web/components/empty_states.templ.
   These were spread across three page stylesheets — .empty-nudge in
   dashboard.css, .pr-empty in pr.css, .tree-empty in repo.css — while their
   markup was used by nine pages between them. A component styled from one
   page's file is one a restyle of that page silently restyles everywhere else,
   and, sitting in the `pages` layer, it also outranked the component rules it
   was surrounded by (CLAUDE.md — shared markup takes its CSS along into
   components/). The design's States artboard treats them as one family; so
   does this file.

   Three treatments, three jobs:
     .empty-nudge  — a whole surface with nothing in it yet, framed and
                     inviting: title, body, the action that fills it.
     .pr-empty     — a list with no rows, in the space the rows would occupy.
     .tree-empty   — a directory listing with no entries, one quiet line. */

/* ---- .empty-nudge: "there is nothing here YET, and here is how to start" ---- */
.empty-nudge {
  position: relative; border: 1px dashed var(--accent-line); border-radius: var(--radius); padding: 30px 26px;
  background: color-mix(in oklab, var(--accent) 5%, var(--bg-elev)); display: grid; gap: 10px; justify-items: start; overflow: hidden;
}
.empty-nudge::before {
  content: ""; position: absolute; top: -60px; right: -60px; width: 160px; height: 160px; border-radius: 50%;
  background: radial-gradient(closest-side, var(--accent-soft), transparent 70%); pointer-events: none;
}
.empty-nudge.small { padding: 18px 16px; }
.empty-nudge-title { margin: 0; font-weight: 700; font-size: 1.08rem; }
.empty-nudge-body { margin: 0; color: var(--fg-muted); font-size: .92rem; max-width: 46ch; }
.empty-nudge-body a { color: var(--accent); }
.empty-nudge-body a:hover { text-decoration: underline; }

/* ---- .pr-empty: an empty list (merge requests, issues, filtered runs) ---- */
.pr-empty {
  padding: 44px; text-align: center; color: var(--fg-muted);
  border: 1px dashed var(--line); border-radius: var(--radius);
  background: color-mix(in oklab, var(--bg-elev) 50%, transparent);
}
/* The filtered-to-nothing state's whole point is the way back ("Clear the
   filter"). Inheriting the box's muted grey made the one affordance in it
   indistinguishable from the sentence around it. */
.pr-empty a { color: var(--accent); }
.pr-empty a:hover { text-decoration: underline; }

/* ---- .tree-empty: a directory with no entries ---- */
.tree-empty { padding: 20px 14px; color: var(--fg-dim); font-size: .9rem; }
}

/* ==== css/components/consent.css ==== */
@layer components {
/* Analytics consent banner (analyticsConsentBanner). Rendered `hidden`; analytics.js
   reveals it only for a visitor who hasn't chosen yet, then remembers the choice. */
.consent-banner { position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 200;
  max-width: 620px; margin-inline: auto; flex-wrap: wrap;
  align-items: center; gap: 10px 16px; padding: 14px 16px;
  background: var(--bg-elev); border: 1px solid var(--bg-elev-2); border-radius: 12px;
  box-shadow: 0 8px 30px rgba(0,0,0,.28); }
/* display goes on :not([hidden]) so it outranks the UA `[hidden]{display:none}` when
   shown, but LETS `hidden` hide it — a plain `.consent-banner{display:flex}` has the
   same specificity as `[hidden]` and, coming later, would override it so the banner
   never hid after Accept/Decline. */
.consent-banner:not([hidden]) { display: flex; }
.consent-banner .consent-text { margin: 0; flex: 1 1 260px; font-size: var(--text-sm); color: var(--fg); }
.consent-banner .consent-actions { display: flex; gap: 8px; flex: 0 0 auto; }
}

/* ==== css/components/storage-notice.css ==== */
@layer components {
/* Storage-quota banner (storage_notice.templ) — shown on the dashboard and the
   org usage page at >=80%/100% of the storage pool (storage-quotas.md §2,
   Phase B). Built on the shared .banner chrome (components/chrome.css), whose
   severity modifiers (.banner-warn / .banner-critical / .banner-error /
   .banner-ok) all live there now, in ONE rule set per severity rather than one
   per caller — .banner-critical and .banner-error were about to become two
   spellings of the same danger tint. What remains here is this component's own
   layout. */
.storage-notice { border-radius: var(--radius); margin: 0 0 20px 0; text-align: left; }
.storage-notice-text { flex: 1 1 auto; }
}

/* ==== css/components/confirm-dialog.css ==== */
@layer components {
/* confirm-dialog — the modal behind the design system's destructive-action
   confirmation convention (see web/frontend/src/islands/confirm-submit.ts and
   the @ConfirmForm templ component). The island builds this markup at submit
   time; nothing server-rendered carries these classes, which is why the file
   is tiny and has no state variants.

   Built entirely from tokens: the dialog is the same bordered elevated surface
   as .side-panel / .settings-item, and the buttons are the system's own
   .btn.ghost / .btn.ghost.danger — there is no bespoke dialog button. */

.confirm-dialog {
  max-width: 34rem;
  padding: 20px 22px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-elev);
  color: var(--fg);
  box-shadow: var(--shadow-lg);
}

/* ::backdrop does NOT inherit the page's custom properties (it has no parent
   element), so this one colour cannot be a var() — it is the single deliberate
   literal in this file, and it is theme-neutral by design: a dim over whatever
   is behind, in both palettes. */
.confirm-dialog::backdrop { background: rgba(1, 4, 9, .55); }

.confirm-dialog-message { margin: 0 0 18px; font-size: var(--text-base); line-height: 1.5; }

.confirm-dialog-actions { display: flex; justify-content: flex-end; gap: var(--sp-2); }
}

/* ==== css/pages/marketing.css ==== */
@layer pages {
/* ---- hero ---- */
.hero { padding: 52px 0 60px; }
.hero h1 {
  font-size: var(--text-3xl); line-height: 1.02; letter-spacing: -.038em;
  margin: 0 0 18px; font-weight: 800; max-width: 15ch;
}
.hero h1 .grad {
  background: linear-gradient(112deg, var(--accent), var(--accent-2) 70%, var(--accent));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero p.lead { font-size: var(--text-lg); color: var(--fg-muted); max-width: 52ch; margin: 0 0 30px; }
.hero .cta { display: flex; gap: 12px; flex-wrap: wrap; }
.hero-grid { display: grid; grid-template-columns: 1fr; gap: 48px; align-items: center; }
@media (min-width: 57rem) { .hero-grid { grid-template-columns: 1.05fr .95fr; } .hero { padding: 96px 0 72px; } }

/* the signature: a portal ring the terminal steps out of */
.hero-visual { position: relative; display: grid; place-items: center; }
.portal {
  position: absolute; z-index: 0; width: min(520px, 108%); aspect-ratio: 1; border-radius: 50%;
  background: conic-gradient(from 0deg,
    var(--portal-2), var(--portal-1), var(--portal-3), var(--portal-1), var(--portal-2), var(--portal-3), var(--portal-2));
  -webkit-mask: radial-gradient(closest-side, transparent 55%, #000 60%, #000 80%, transparent 84%);
          mask: radial-gradient(closest-side, transparent 55%, #000 60%, #000 80%, transparent 84%);
  filter: blur(3px) saturate(1.25);
  opacity: .85; animation: portal-spin 22s linear infinite;
}
.portal::after {
  content: ""; position: absolute; inset: 14%; border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--accent) 30%, transparent), transparent 72%);
  filter: blur(22px);
}
@keyframes portal-spin { to { transform: rotate(360deg); } }
.hero-visual .term { position: relative; z-index: 1; width: 100%; }
@media (max-width: 900px) { .portal { width: min(420px, 90%); opacity: .55; } }

/* ---- terminal card ---- */
.term {
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  background: color-mix(in oklab, var(--bg-elev) 88%, transparent);
  backdrop-filter: blur(6px); box-shadow: var(--shadow-lg);
}
.term .bar { display: flex; align-items: center; gap: 8px; padding: 11px 14px; border-bottom: 1px solid var(--line-soft); background: var(--bg-elev-2); }
.term .dot { width: 11px; height: 11px; border-radius: 50%; background: var(--line); }
.term .dot:nth-child(1) { background: #ff6b6b; }
.term .dot:nth-child(2) { background: #ffd166; }
.term .dot:nth-child(3) { background: var(--success); }
.term .bar .t { margin-left: 8px; color: var(--fg-dim); font: .78rem var(--mono); }
.term pre { margin: 0; padding: 18px 18px 22px; font: .86rem/1.7 var(--mono); overflow-x: auto; }
.term .p { color: var(--accent); }
.term .c { color: var(--fg-dim); }
.term .o { color: var(--fg-muted); }

/* ---- sections & feature cards ---- */
.section { padding: var(--sp-section) 0; }
.section h2 { font-size: var(--text-2xl); letter-spacing: -.03em; margin: 0 0 8px; line-height: 1.1; }
.section .sub { color: var(--fg-muted); margin: 0 0 36px; font-size: var(--text-lg); }
/* Feature-card gallery: intrinsically responsive (auto-fit) — no bespoke
   breakpoint needed; cards wrap to 1-up when the row gets narrow. */
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 18px; }
.card {
  position: relative; border: 1px solid var(--line); border-radius: var(--radius); padding: 24px;
  background: var(--bg-elev);
  transition: border-color .15s, box-shadow .15s; overflow: hidden;
}
.card:hover { border-color: color-mix(in oklab, var(--fg) 22%, var(--line)); box-shadow: var(--shadow-sm); }
.card .ic {
  width: 38px; height: 38px; border-radius: var(--radius); display: grid; place-items: center; font-size: 1.1rem;
  background: var(--accent-soft); color: var(--accent); margin-bottom: 14px;
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.card h3 { margin: 0 0 6px; font-size: 1.08rem; letter-spacing: -.01em; }
.card p { margin: 0; color: var(--fg-muted); font-size: .94rem; }

/* #21 — hero terminal: wrap long lines so the last line never clips. */
.term pre { white-space: pre-wrap; overflow-wrap: anywhere; }

/* ---- pricing, plans & billing ---------------------------------------------- */
.pricing-title { font-size: var(--text-3xl); letter-spacing: -.03em; line-height: 1.05; margin: 10px 0 6px; }
.pricing-title .grad {
  background: linear-gradient(90deg, var(--portal-1), var(--portal-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
/* Intrinsically responsive plan gallery (auto-fit) — no bespoke breakpoint. */
.pricing-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 18px; margin: 28px 0 20px; align-items: start; }
.plan-card {
  position: relative; border: 1px solid var(--line); border-radius: var(--radius); padding: 26px 24px;
  background: color-mix(in oklab, var(--bg-elev) 82%, transparent);
  display: flex; flex-direction: column; gap: 14px; min-width: 0;
}
.plan-card.plan-popular { border-color: var(--accent-line); box-shadow: var(--glow); }
.plan-ribbon {
  position: absolute; top: -11px; left: 24px; font: 600 var(--text-xs)/1 var(--sans);
  text-transform: uppercase; letter-spacing: .06em; color: var(--accent-ink);
  background: var(--accent); padding: 5px 10px; border-radius: var(--pill);
}
.plan-name { margin: 0; font-size: 1.25rem; letter-spacing: -.02em; }
.plan-price { margin: 0; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.plan-price-amount { font-size: 2rem; font-weight: 750; letter-spacing: -.02em; }
.plan-price-note { color: var(--fg-dim); font: var(--text-sm) var(--mono); }
.plan-lines { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.plan-lines li { position: relative; padding-left: 22px; color: var(--fg-muted); font-size: .94rem; }
.plan-lines li::before { content: "✓"; position: absolute; left: 0; color: var(--success); font-weight: 700; }
.plan-card .btn { margin-top: auto; text-align: center; justify-content: center; }
.pricing-foot { color: var(--fg-dim); font-size: var(--text-sm); margin-top: 10px; }

/* Runner sizes: a quiet band under the plan gallery. Same auto-fit rule as the
   plan grid, so it reflows without a bespoke breakpoint. */
.runner-tiers {
  margin: 26px 0 4px; padding: 20px 22px; border: 1px solid var(--line);
  border-radius: var(--radius); background: color-mix(in oklab, var(--bg-elev) 60%, transparent);
}
.runner-tiers-title { margin: 0 0 4px; font-size: 1.05rem; letter-spacing: -.01em; }
.runner-tiers-sub { margin: 0 0 14px; color: var(--fg-muted); font-size: .94rem; }
.runner-tiers-list {
  list-style: none; margin: 0; padding: 0; display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
}
.runner-tiers-list li {
  color: var(--fg-muted); font: var(--text-sm) var(--mono);
  padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--pill);
  min-width: 0; overflow-wrap: anywhere;
}

/* Plan gate: a locked nav entry points at billing with a small marker. */

/* final home CTA band */
.home-final { text-align: center; }
.home-final .cta { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
}

/* ==== css/pages/repo.css ==== */
@layer pages {
/* ---- repo / code view chrome ---- */
.repo-head { display: flex; align-items: center; gap: 12px; padding: 26px 0 18px; flex-wrap: wrap; }
.repo-head .path { font-size: 1.35rem; font-weight: 700; letter-spacing: -.02em; }
.repo-head .path .sep { color: var(--fg-dim); margin-inline: 4px; }
.repo-head .path a { color: inherit; }
.repo-head .path a:hover { color: var(--accent); }
.repo-head .btn { padding: 6px 12px; font-size: .85rem; }
/* A PILL DOES NOT WRAP. `white-space: nowrap` is on all five of the design
   system's chip/badge rules (`components/chip.css`), on both of its nav-tab
   ones (`components/tabs.css`) and on `.rule-flag` (`components/form.css`);
   the repo carried none of the eight, which — being three canvas files missed
   in one direction — is one bad sync rather than eight decisions.
   The shape is the whole point of a pill — a two-word label breaking across
   lines inside a --pill radius reads as a rendering fault, and the labels ARE
   two words ("pre-release", "MR required", "no force-push", "Pull requests",
   a release name). Nothing regresses in a narrow slot: ref names cannot contain
   spaces (git forbids them) so a tag/branch chip never had a break opportunity
   to lose, and no ancestor sets overflow-wrap/word-break over these.
   The nowrap on the other five rules below, and on .count-badge in
   components/labels.css, is this same rule; pinned by TestPillsDoNotWrap. */
.badge {
  font-size: .68rem; font-weight: 600; letter-spacing: .04em; text-transform: lowercase;
  white-space: nowrap;
  color: var(--fg-muted); border: 1px solid var(--line); border-radius: var(--pill);
  padding: 3px 10px; background: var(--bg-elev);
}
/* A frozen repository (migration 0081). Warn, not danger: nothing is lost and
   the state is reversible — it is a status, not an alarm. */
.badge.archived {
  color: var(--warn); border-color: color-mix(in oklab, var(--warn) 45%, var(--line));
  background: color-mix(in oklab, var(--warn) 12%, transparent);
}

.repo-layout { display: grid; grid-template-columns: 1fr; gap: 20px; padding-bottom: 60px; }
@media (min-width: 50rem) { .repo-layout { grid-template-columns: 240px 1fr; } }
.tree { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); padding: 8px; height: max-content; }
.tree li { list-style: none; }
.tree ul { margin: 0; padding: 0; }
.tree a { display: flex; align-items: center; gap: 9px; padding: 7px 10px; border-radius: 7px; color: var(--fg-muted); font-size: .9rem; }
.tree a:hover { background: var(--bg-elev-2); color: var(--fg); }
.tree a.active { background: var(--accent-soft); color: var(--fg); }

.codepanel { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--bg-elev); box-shadow: var(--shadow-sm); }
/* gap + a .spacer element rather than space-between: the toolbar now holds a
   variable number of trailing actions (Raw, and later Blame/Copy), and
   space-between spreads THREE children to the edges instead of keeping the
   actions grouped on the right. */
.codepanel .fbar { display: flex; align-items: center; gap: var(--sp-3); padding: 10px 14px; border-bottom: 1px solid var(--line-soft); background: var(--bg-elev-2); font: .84rem var(--mono); color: var(--fg-muted); }
/* Chroma renders a file as <div class="chroma"><table class="lntable"> with the
   line-number gutter in its own <td> (see blobFormatter, web/highlight.go). The
   OUTER .chroma is the scroll container and carries the panel's padding; the two
   inner <pre class="chroma"> cells must repeat neither, or the gutter would
   scroll independently of the code it numbers. */
.codepanel .chroma { margin: 0; font: .84rem/1.6 var(--mono); padding: 8px 4px; overflow-x: auto; }
.codepanel .chroma .chroma { padding: 0; overflow: visible; }
.codepanel .chroma .lntable { width: 100%; }
/* The gutter: dim, unselectable, and — being a separate table column — outside
   any selection made in the code column, so copying code copies code. */
.codepanel .chroma .lnt { color: var(--fg-dim); padding: 0 16px 0 6px; user-select: none; }
/* Row hover. The band covers the code cell (the gutter is a sibling column, so a
   number cannot join its row's highlight without JS — an accepted trade for the
   clean copy the table gives us). The tint is fixed rather than themed: the
   highlight theme is github-dark in both app themes, so this always sits on a
   dark ground. */
.codepanel .chroma .line:hover { background: var(--code-row-hover); }

/* ---- blame ----
   The blame view is the same code panel with an attribution column in front of
   the gutter. It is a real <table> so a run of lines from one commit is ONE
   rowspan cell: the visual grouping and the row alignment are then the same
   fact, instead of a column of fixed-height boxes that drift out of step with
   the code the first time a line-height changes. The wrapper is the scroll
   container (it carries .chroma, so the rules above give it the panel's font,
   padding and overflow); border-collapse keeps the rowspan cell's separator a
   single hairline. */
.blame-wrap .blame { border-collapse: collapse; width: 100%; }
.blame-attr {
  /* Fixed and never wider: the attribution is context, the code is the content.
     A commit subject long enough to push the code off-screen is truncated
     rather than allowed to reflow the file. The colours are the --code-*
     tokens, not the themed ones: this cell sits ON the Chroma ground, which is
     github-dark in both app themes. */
  width: 22rem; max-width: 22rem; padding: 2px 14px 2px 8px;
  border-right: 1px solid var(--code-line); vertical-align: top;
  font: .78rem/1.5 var(--sans); color: var(--code-fg-dim);
  background: var(--code-attr-bg);
}
.blame-line { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.blame-subject { color: var(--code-fg-muted); }
a.blame-subject:hover { color: var(--code-link); text-decoration: underline; }
.blame-meta { display: flex; gap: 8px; align-items: baseline; }
.blame-sha { font-family: var(--mono); color: var(--code-link); font-weight: 600; }
.blame-sha:hover { text-decoration: underline; }
.blame-author { color: var(--code-fg-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.blame-author:hover { color: var(--code-fg-muted); text-decoration: underline; }
.blame-date { color: var(--code-fg-dim); margin-left: auto; white-space: nowrap; }
/* The code cell: same monospace ground as the plain view, one line per row, no
   wrapping (the wrapper scrolls instead — a wrapped line would desynchronise
   the attribution cell's rowspan from what the reader sees). */
.blame-code { white-space: pre; padding: 0 0 0 8px; width: 100%; }
.blame-row:hover .blame-code { background: var(--code-row-hover); }
/* The Blame toggle's pressed state, on the file toolbar. */
.fbar-on { color: var(--fg); border-bottom: 2px solid var(--accent-2); }

/* ---- engine-backed explorer ---- */
/* The description now lives inside the About side panel, so it no longer needs
   the negative top margin that pulled it up under the repo nav. */
/* Two margins, because .repo-desc has two homes. Standing under the repo head it
   pulls up into the heading's trailing space (-6px) and gives the tree room
   below (16px); inside a .side-panel card the card owns the padding, so it is
   the tighter 0/10px. This file carried the SIDEBAR value as the base rule, so
   the description sat wrong anywhere else it appeared. */
.repo-desc { color: var(--fg-muted); margin: -6px 0 16px; font-size: .96rem; max-width: 72ch; }
.side-panel .repo-desc { margin: 0 0 10px; }
/* About panel facts line: "Go · MIT license · 68 commits on main". The licence
   fact links the file it was read from, so the reader can check the terms
   themselves — it stays in the line's dim voice until hovered. */
.side-panel-facts { margin: 0; font-size: .85rem; color: var(--fg-dim); }
.side-panel-facts a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.side-panel-facts a:hover { color: var(--accent); }
/* Clone widget: HTTPS/SSH toggle (pure CSS via radio + :has()) + copy buttons. */
.clone-widget { margin: 0 0 18px; }
.clone-toggle { display: inline-flex; gap: 4px; margin-bottom: 8px; }
/* Visually hidden but focusable radio inputs drive the toggle. */
.clone-proto {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.clone-tab {
  cursor: pointer; user-select: none; font-size: .78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--fg-muted);
  padding: 5px 12px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--bg-elev); transition: color .15s, background .15s, border-color .15s;
}
.clone-proto:checked + .clone-tab {
  color: var(--accent); background: var(--accent-soft); border-color: var(--accent);
}
.clone-proto:focus-visible + .clone-tab {
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent);
}
.clone-row {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 8px 8px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-elev);
}
/* Show only the row matching the checked protocol; default to HTTPS. */
.clone-widget:has(.clone-proto-https:checked) .clone-row-ssh { display: none; }
.clone-widget:has(.clone-proto-ssh:checked) .clone-row-https { display: none; }
.clone-cmd { flex: 1; font-family: var(--mono); font-size: .86rem; color: var(--fg-muted); word-break: break-all; }
.clone-copy {
  cursor: pointer; flex-shrink: 0; font-size: .78rem; font-weight: 600; color: var(--fg);
  padding: 5px 12px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg);
  transition: color .15s, background .15s, border-color .15s;
}
.clone-copy:hover { border-color: var(--accent); color: var(--accent); }
.clone-copy.copied { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* Compact "Clone ▾" header dropdown (native <details>, no JS) wrapping the
   clone widget. Sits in the header action row, left of Settings. */
.clone-dropdown { position: relative; }
.clone-dropdown > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-elev); font-size: .85rem; font-weight: 600; color: var(--fg);
  transition: border-color .15s;
}
.clone-dropdown > summary::-webkit-details-marker { display: none; }
.clone-dropdown > summary:hover { border-color: var(--accent-line); }
.clone-dropdown[open] > summary { border-color: var(--accent-line); }
.clone-ic { color: var(--accent); }
.clone-caret { color: var(--fg-dim); font-size: .7rem; }
.clone-menu {
  position: absolute; z-index: 40; right: 0; margin-top: 6px; min-width: 34rem; max-width: 92vw;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-elev);
  box-shadow: var(--shadow); padding: 12px;
}
.clone-menu .clone-widget { margin: 0; }
/* Keep the clone URL on one line in the dropdown; scroll rather than wrap so the
   full command is readable regardless of host/repo length. */
.clone-menu .clone-cmd { white-space: nowrap; overflow-x: auto; word-break: normal; }

/* branch/tag switcher (native <details> dropdown, no JS) */
.ref-switch { position: relative; }
.ref-switch > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 14px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-elev); font-size: .88rem; font-weight: 600; color: var(--fg);
  transition: border-color .15s;
}
.ref-switch > summary::-webkit-details-marker { display: none; }
.ref-switch > summary:hover { border-color: var(--accent-line); }
.ref-switch[open] > summary { border-color: var(--accent-line); }
.ref-icon { color: var(--accent); }
.ref-menu {
  position: absolute; z-index: 40; margin-top: 6px; min-width: 220px; max-height: 340px; overflow-y: auto;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-elev);
  box-shadow: var(--shadow); padding: 6px;
}
.ref-group { font: .7rem var(--mono); text-transform: uppercase; letter-spacing: .08em; color: var(--fg-dim); padding: 8px 10px 4px; }
.ref-item { display: block; padding: 7px 10px; border-radius: 6px; font-size: .88rem; color: var(--fg-muted); }
.ref-item:hover { background: var(--bg-elev-2); color: var(--fg); }
.ref-item.active { color: var(--fg); font-weight: 600; background: var(--accent-soft); }
.ref-all-tags {
  display: block; margin-top: 6px; padding: 8px 10px; border-top: 1px solid var(--line-soft);
  font-size: .82rem; font-weight: 600; color: var(--accent-2);
}
.ref-all-tags:hover { color: var(--accent); }

/* repo-home two-column layout: main content + right sidebar (stacks on narrow
   screens; the sidebar never causes horizontal scroll). */
.repo-home-cols { display: grid; grid-template-columns: 1fr; gap: 28px; align-items: start; }
.repo-home-cols .explorer { min-width: 0; }
@media (min-width: 57rem) { .repo-home-cols { grid-template-columns: minmax(0, 1fr) 300px; } }
.repo-side { min-width: 0; }
.side-panel { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); padding: 16px; }
.side-panel-head { margin: 0 0 12px; font-size: .95rem; font-weight: 700; letter-spacing: -.01em; }
.side-panel-empty { margin: 0 0 8px; color: var(--fg-dim); font-size: .88rem; }
.side-panel-all { display: inline-block; margin-top: 10px; font-size: .84rem; font-weight: 600; color: var(--accent-2); }
.side-panel-all:hover { color: var(--accent); }
/* Stacked sidebar cards need air between them, and the last line inside a card
   must not add a margin the card's own padding already provides. Both from the
   design system; without the first, About and Releases butt together. */
.repo-side .side-panel + .side-panel { margin-top: 16px; }
.side-panel > .side-panel-empty:last-child { margin: 0; }
.release-card { display: block; padding: 12px; border: 1px solid var(--line-soft); border-radius: var(--radius-sm); background: var(--bg-elev-2); }
.release-card:hover { border-color: var(--accent-line); }
.release-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.release-card-title { font-weight: 600; color: var(--fg); overflow: hidden; text-overflow: ellipsis; }
.release-card-meta { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.release-card-date { font: .78rem var(--mono); color: var(--fg-dim); }

/* breadcrumbs */
.crumbs { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; padding: 4px 0 16px; font-size: .95rem; font-family: var(--mono); }
.crumbs a { color: var(--accent-2); font-weight: 600; }
.crumbs a:hover { text-decoration: underline; }
.crumb-sep { color: var(--fg-dim); }
.crumb-cur { color: var(--fg); font-weight: 600; }

/* tree table */
.explorer { padding-bottom: 60px; }
.last-commit {
  display: flex; align-items: center; gap: 12px; padding: 10px 14px; font-size: .88rem;
  border: 1px solid var(--line); border-bottom: 0; border-radius: var(--radius) var(--radius) 0 0;
  background: var(--bg-elev-2); color: var(--fg-muted);
}
.lc-sha { font-family: var(--mono); font-size: .82rem; color: var(--accent); }
.lc-subject { color: var(--fg); font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The subject is the only element on this row allowed to shrink. Without it a
   long commit subject pushes the metadata off the right edge instead of
   ellipsising, because .last-commit is a flex row and every other child is
   nowrap. `flex: 0 1 auto` keeps it content-sized until the row is full, then
   lets its own text-overflow do the work. */
.last-commit .lc-subject { flex: 0 1 auto; min-width: 0; }
.lc-meta { font: .82rem var(--mono); color: var(--fg-dim); white-space: nowrap; }
/* The signature badge sits inside .lc-meta now; it must not inherit the mono
   voice the rest of the metadata uses. */
.lc-meta .sig-badge { font-family: var(--sans); }
/* The ref the "latest commit" claim is relative to. Lifted out of the dim meta
   colour because it is the qualifier that makes the rest of the line true, not
   another piece of trailing metadata. */
.lc-ref { color: var(--fg-muted); font-weight: 600; }
.tree-table { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--bg-elev); }
.last-commit + .tree-table { border-radius: 0 0 var(--radius) var(--radius); }
.tree-row {
  display: grid; grid-template-columns: 22px 1fr auto; align-items: center; gap: 10px;
  padding: 9px 14px; border-bottom: 1px solid var(--line-soft); font-size: .92rem; color: var(--fg-muted);
  transition: background .1s;
}
.tree-row:last-child { border-bottom: 0; }
.tree-row:hover { background: var(--bg-elev-2); color: var(--fg); }
.tree-ic { text-align: center; }
.tree-ic.dir { color: var(--accent-2); }
.tree-ic.file { color: var(--fg-dim); }
.tree-name { color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tree-size { font-family: var(--mono); font-size: .78rem; color: var(--fg-dim); }
/* .tree-empty moved to components/empty-states.css, beside the other two
   empty-state treatments (the design's States artboard treats them as one
   family, and treeEmpty is a shared component). */

/* README panel. The markdown TYPOGRAPHY it wraps (.markdown-body) moved to
   components/markdown-body.css: nine surfaces render that class, and from this
   page file its padding outranked — `pages` over `components` — the padding
   every component asked for around its own markdown. The README's box stays
   here; what is inside the box is shared. */
.readme { margin-top: 24px; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--bg-elev); }
.readme-head { padding: 10px 16px; border-bottom: 1px solid var(--line-soft); background: var(--bg-elev-2); font: .78rem var(--mono); font-weight: 600; letter-spacing: .04em; color: var(--fg-muted); }

/* blob view extras */
.fbar-lang { margin-left: 10px; color: var(--fg-dim); }
.fbar-raw { color: var(--accent-2); font-weight: 600; }
.fbar-raw:hover { text-decoration: underline; }
.file-notice {
  padding: 10px 14px; font-size: .86rem; color: var(--fg);
  background: color-mix(in oklab, var(--accent-2) 14%, var(--bg-elev));
  border-bottom: 1px solid var(--line-soft);
}
.binary-file { display: grid; place-items: center; gap: 14px; padding: 48px 20px; color: var(--fg-muted); }

/* git-lfs blob panel */
.lfs-body { display: grid; place-items: center; gap: 14px; padding: 40px 20px; color: var(--fg-muted); }
.lfs-image { max-width: 100%; height: auto; border-radius: 6px; border: 1px solid var(--line-soft); background: var(--bg-elev); }
.lfs-note { margin: 0; font-size: .9rem; }

/* commit log */
.commit-list { list-style: none; margin: 0 0 60px; padding: 0; display: grid; gap: 8px; }
.commit-item {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 12px 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev);
  transition: border-color .15s, transform .12s;
}
.commit-item:hover { border-color: var(--accent-line); transform: translateX(2px); }
.commit-main { display: grid; gap: 4px; min-width: 0; }
.commit-subject { font-weight: 600; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.commit-meta { font: .8rem var(--mono); color: var(--fg-dim); }
.commit-sha { font-family: var(--mono); font-size: .82rem; color: var(--accent); white-space: nowrap; }
/* Day-grouped log: the flat list's 60px tail is the gap before the pager, so it
   must not repeat between every date subhead — the groups are one list visually,
   and only the last of them ends the page. */
.commit-days .commit-list { margin-bottom: 22px; }
.commit-days > .settings-subhead-sm:first-child { margin-top: 0; }

/* ---- repo section nav (Code / Pull requests) ---- */
.repo-nav { display: flex; gap: 4px; margin: 10px 0 18px; border-bottom: 1px solid var(--line-soft); }
/* nowrap: a tab label is one line ("Pull requests", "Merge requests") — see the
   .badge block above. .repo-nav is a non-wrapping flex row, so a tab that broke
   internally would only make the bar taller, never narrower. */
.repo-nav-link {
  padding: 8px 14px; color: var(--fg-muted); font-size: .9rem; font-weight: 500;
  border-bottom: 2px solid transparent; margin-bottom: -1px; display: inline-flex; align-items: center; gap: 6px;
  white-space: nowrap;
  transition: color .12s;
}
.repo-nav-link:hover { color: var(--fg); }
.repo-nav-link.active { color: var(--fg); border-bottom-color: var(--accent); }

/* ---- feature: star / watch, tags, releases, signatures, branch protection ---- */

/* Star / Watch engagement controls in the repo header. */
.repo-engage { display: inline-flex; gap: 8px; margin-right: 8px; }
.engage-btn { display: inline-flex; align-items: center; gap: 6px; }
.engage-btn.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.engage-ic { font-size: .95rem; line-height: 1; }

/* Star count shown on repo list items. */
.star-count { font-size: .78rem; color: var(--fg-muted); white-space: nowrap; }

/* Tag chip + commit-decoration + signature badges. */
.tag-chip {
  display: inline-block; font: 600 .72rem var(--mono); color: var(--accent);
  white-space: nowrap;
  border: 1px solid var(--accent); border-radius: var(--pill);
  padding: 1px 8px; margin-left: 6px; background: var(--accent-soft); vertical-align: middle;
}
.sig-badge {
  display: inline-block; font-size: .68rem; font-weight: 700; letter-spacing: .03em;
  white-space: nowrap;
  border-radius: var(--pill); padding: 1px 8px; margin-left: 6px; vertical-align: middle;
}
.sig-verified { color: #1a7f37; border: 1px solid #1a7f37; background: color-mix(in oklab, #1a7f37 12%, transparent); }
.sig-unverified { color: var(--fg-muted); border: 1px solid var(--line); background: var(--bg-elev); }
.sig-by { font-size: .72rem; color: var(--fg-muted); margin-left: 4px; vertical-align: middle; }
.commit-author { color: var(--fg-muted); }
a.commit-author:hover { color: var(--accent); }
/* Branch-tip + release decoration chips, styled to sit beside .tag-chip. */
.branch-chip {
  display: inline-block; font: 600 .72rem var(--mono); color: var(--fg-muted);
  white-space: nowrap;
  border: 1px solid var(--line); border-radius: var(--pill);
  padding: 1px 8px; margin-left: 6px; background: var(--bg-elev); vertical-align: middle;
}
/* "latest" / release chip. The colour is --success (green = success status,
   per the token rules), NOT the literal #1a7f37 it used to hard-code — that
   was the LIGHT-theme value of --success baked in, so the chip stayed dark
   green against a dark background and failed contrast in dark mode. */
/* nowrap: this is the one of the eight whose text is free-form — a release NAME, which
   may carry spaces ("v1.2 — Get Schwifty"), unlike the ref names beside it. The
   design system says nowrap for it anyway and that is the right call: the chip
   sits inline after a commit subject in the wide main column, so a long name
   lengthens a line that already reflows, whereas wrapping breaks the pill on
   every release whose name has a space in it. */
.release-chip {
  display: inline-block; font: 600 .72rem var(--mono); color: var(--success); text-decoration: none;
  white-space: nowrap;
  border: 1px solid var(--success); border-radius: var(--pill);
  padding: 1px 8px; margin-left: 6px; background: var(--success-soft);
  vertical-align: middle;
}
.release-chip:hover { background: color-mix(in oklab, var(--success) 22%, transparent); }
/* Commit rows: subject + SHA are links to the commit-detail page. */
.commit-link { color: inherit; text-decoration: none; }
.commit-link:hover { color: var(--accent); text-decoration: underline; }
a.commit-sha:hover { text-decoration: underline; }
a.lc-sha { text-decoration: none; }
a.lc-sha:hover { text-decoration: underline; }

/* Commit-detail page. */
.commit-detail { margin-bottom: 60px; }
.commit-detail-head {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-elev); padding: 16px 18px; margin-bottom: 20px;
}
.commit-detail-subject { margin: 0 0 8px; font-size: 1.15rem; color: var(--fg); }
.commit-detail-body {
  margin: 0 0 12px; padding: 12px 14px; white-space: pre-wrap; word-break: break-word;
  font: .86rem/1.5 var(--mono); color: var(--fg-dim);
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--radius);
}
.commit-detail-meta { display: flex; align-items: center; gap: 8px; font-size: .86rem; color: var(--fg-dim); }
.commit-detail-ids {
  display: flex; align-items: center; gap: 16px; margin-top: 12px;
  padding-top: 12px; border-top: 1px solid var(--line); font-size: .82rem;
}
.commit-parents { color: var(--fg-dim); }

/* Markdown blob: Raw / Rendered toggle (pure CSS via radios + :has()). */
.md-toggle { display: inline-flex; gap: 4px; }
.md-mode {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.md-tab {
  cursor: pointer; user-select: none; font-size: .72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--fg-muted);
  padding: 3px 10px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg-elev);
}
.md-mode:checked + .md-tab { color: var(--accent); background: var(--accent-soft); border-color: var(--accent); }
.md-mode:focus-visible + .md-tab { box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent); }
.md-view { padding: 4px 0; }
.md-view-rendered { padding: 20px 22px; }
.md-blob:has(.md-mode-rendered:checked) .md-view-raw { display: none; }
.md-blob:has(.md-mode-raw:checked) .md-view-rendered { display: none; }

/* Releases. Each row is a CARD, not a rule-separated row: a release is a
   self-contained announcement (title, chips, author, notes) and the flat list
   made a long body run straight into the next release's title with nothing but
   a hairline to say where one ended. The design's 10px radius and 22/24px
   padding are rounded onto the system's own scale (--radius-lg, --sp-5); a raw
   number here would be a token the next palette rework silently misses. */
.release-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-4); }
/* ---- releases list, the design system's card treatment ----
   The system gates cards behind a .release-cards MODIFIER (the bare
   .release-list is still the flat-row default it uses elsewhere), splits a card
   into .release-row > .release-main + .release-actions so the actions occupy a
   right-hand column instead of trailing the title after a spacer, and separates
   the notes with .release-rule. Ported from css/pages/releases.css; the numbers
   are rounded onto the token scale as the rest of this file is. */
.release-list.release-cards { display: grid; gap: 18px; }
.release-cards .release-item {
  padding: 22px 24px; border: 1px solid var(--line);
  border-radius: 10px; background: var(--bg-elev);
}
.release-row { display: flex; align-items: flex-start; gap: 12px; }
.release-main { flex: 1; min-width: 0; }
.release-actions { display: flex; gap: 8px; flex: none; }
.release-rule { border: none; border-top: 1px solid var(--line-soft); margin: 14px 0; }
.release-summary > p:only-child { margin: 0; }
/* toolbar above the list: sub-nav, spacer, then the find field */
.releases-toolbar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 18px 0 22px; }
.releases-toolbar .repo-nav { border-bottom: none; margin: 0; flex: none; }
.releases-toolbar .toolbar-spacer { flex: 1; }
.release-search {
  width: 200px; padding: 7px 12px; font-size: .88rem; color: var(--fg);
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-elev);
}
.release-search:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* prev/next pager inside a .side-panel card */
.side-pager { display: flex; justify-content: space-between; align-items: center; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line-soft); font-size: .84rem; }
.side-pager .is-disabled { color: var(--fg-dim); }
.side-pager .side-panel-all { margin: 0; }
.release-item {
  padding: var(--sp-5); border: 1px solid var(--line);
  border-radius: var(--radius-lg); background: var(--bg-elev);
}
.release-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.release-title { font-weight: 700; font-size: 1.05rem; color: var(--fg); }
.release-title:hover { color: var(--accent); }
.release-meta { color: var(--fg-muted); font-size: .84rem; margin: 4px 0; }
.release-summary { max-height: 8.5em; overflow: hidden; position: relative; }
/* The design draws an <hr> between a card's meta block and its notes. It is a
   DIVIDER, not a thematic break, so it is a border on the thing it divides:
   an <hr> would announce a section change to a screen reader that the card's
   own structure contradicts. A card with no notes renders no rule at all. */
.release-item .release-summary { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--line); }
.release-detail-head { margin-bottom: 14px; }
.release-delete { margin-top: 24px; }
/* Downloadable files attached to a release (issue #60). */
.release-assets .settings-item { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.release-asset-name { font-weight: 600; color: var(--fg); word-break: break-all; }
.release-asset-name:hover { color: var(--accent); }
/* An asset row's metadata is size · label · downloads, with the remove control
   riding along at the end — so the row stays one baseline, not two. */
.release-assets .settings-item-meta { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
/* Attach-files form on a release page, and the publish button under a draft. */
.release-asset-form { margin-top: 16px; max-width: 520px; }
.release-publish { margin-top: 20px; }
.release-draft-note { color: var(--fg-muted); font-size: .88rem; margin: 10px 0 4px; }
.badge-pre { color: var(--warn); border-color: var(--warn); background: color-mix(in oklab, var(--warn) 12%, transparent); }
/* A draft release is unannounced, not broken or urgent — muted, not coloured,
   so it reads as "not yet" beside the warn-coloured pre-release chip. */
.badge-draft { color: var(--fg-muted); border-color: var(--line); background: var(--bg-elev-2); }
/* Branch covered by a protection rule (issue #64) — a push to it can be refused,
   so the list has to say so before the refusal does. */
.badge-protected { color: var(--accent); border-color: var(--accent); background: color-mix(in oklab, var(--accent) 12%, transparent); }

/* Checkbox rows (branch protection, releases). */
.check-field { display: flex; align-items: center; gap: 9px; margin: 8px 0; font-size: .92rem; color: var(--fg); cursor: pointer; }
.check-field input { width: auto; }
/* The clearest case of the eight: every branch-protection flag is two or three
   words ("MR required", "no force-push", "3 approvals") and they render as a row
   of pills, so without nowrap the row wraps INSIDE a flag rather than between
   flags. See the .badge block above. */
.rule-flag {
  display: inline-block; font-size: .72rem; color: var(--fg-muted);
  white-space: nowrap;
  border: 1px solid var(--line); border-radius: var(--pill); padding: 1px 8px; margin-right: 5px;
}

.check-field input[type="checkbox"] {
  width: 1.05rem; height: 1.05rem; margin: 0; flex: none; cursor: pointer;
}

/* #19 — empty-repo push snippet follows the clone widget's HTTPS/SSH toggle. */
.repo-quickstart .push-ssh { display: none; }
.repo-quickstart:has(.clone-proto-ssh:checked) .push-https { display: none; }
.repo-quickstart:has(.clone-proto-ssh:checked) .push-ssh { display: block; }

/* ---- repository import status ---- */
.import-status-card .form-ok { display: flex; align-items: center; gap: .6rem; }
.import-spinner {
  width: 1em; height: 1em; flex: none; border-radius: 50%;
  border: 2px solid color-mix(in oklab, currentColor 30%, transparent);
  border-top-color: currentColor; animation: portal-spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .import-spinner { animation: none; } }
}

/* ==== css/pages/auth.css ==== */
@layer pages {
/* ---- auth pages (signup / login / verify) ---- */
.auth { display: grid; place-items: center; padding: 72px 20px; }
.auth-card {
  position: relative; width: 100%; max-width: 424px;
  background: var(--bg-elev); border: 1px solid var(--line);
  border-radius: var(--radius-lg); padding: 34px; box-shadow: var(--shadow-lg); overflow: hidden;
}
.auth-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
}
.auth-card h1 { margin: 0 0 6px; font-size: 1.5rem; letter-spacing: -.02em; }
.auth-sub { margin: 0 0 22px; color: var(--fg-muted); font-size: .95rem; }
.auth-note { margin: 18px 0 0; color: var(--fg-dim); font-size: .84rem; }
.auth-alt { margin: 20px 0 0; text-align: center; color: var(--fg-muted); font-size: .9rem; }
.auth-alt a { color: var(--accent-2); font-weight: 600; }
}

/* ==== css/pages/settings.css ==== */
@layer pages {
/* ---- account settings ---- */
.settings {
  display: grid; grid-template-columns: 1fr; gap: 24px;
  max-width: var(--maxw); margin: 0 auto; padding: 48px 20px;
}
@media (min-width: 48rem) { .settings { grid-template-columns: 220px 1fr; gap: 40px; } }
/* Repo settings sit under the repo chrome (.repo-head + .repo-nav), which lives
   in a .wrap that already applies the page gutter and max width. Re-applying
   them here would inset the settings body past the tabs directly above it, so
   the modifier keeps only the vertical rhythm below the nav. */
.settings.repo-settings { max-width: none; margin: 0; padding: 28px 0 60px; }
/* The sidebar's own chrome (.settings-nav-title, .settings-nav-link, .nav-lock)
   is shared by three settings families and lives with its component, in
   components/settings-nav.css. */
.settings-body { min-width: 0; max-width: 560px; }
/* NOT mono, and this closes #165. 726359c made exactly two titles monospace in
   one edit — this one and .pr-detail-title — on the stated grounds that a
   primary page title carries "the monospace display voice ... matching the mono
   labels/eyebrows already in the system". ec03b7d checked that claim for
   .pr-detail-title and it was false; the design system's own settings.css says
   the same about this one: `.settings-title { margin: 0 0 6px; font-weight: 650;
   font-size: 1.35rem; letter-spacing: -.015em }` — every number 726359c set, and
   NO font-family, so the title inherits the body sans.
   That is the whole divergence here, and the reason the weight is NOT 600 like
   .pr-detail-title's: the design system took 726359c's 650/1.35rem/-.015em and
   left only the family behind, so "make it match the other title" would be a
   second unchecked guess. The mono voice belongs to the identifiers on these
   pages, which keep it (.settings-item-mono, .token-value, .team-slug,
   .mono-stat, .audit-action). Pinned by TestSettingsTitleWearsTheBodyFace. */
.settings-title { margin: 0 0 6px; font-weight: 650; font-size: 1.35rem; letter-spacing: -.015em; }
.settings-sub { margin: 0 0 20px; color: var(--fg-muted); font-size: .93rem; }
.settings-subhead-sm { margin: 20px 0 10px; font-size: .95rem; color: var(--fg); }
.settings-subhead { margin: 32px 0 14px; font-size: 1.05rem; border-top: 1px solid var(--line-soft); padding-top: 24px; }
/* A DIVIDER WITH NOTHING ABOVE IT DIVIDES NOTHING.
   .settings-subhead is a SECTION divider: the border and the 24px above it
   separate it from the section before. Leading its container there is no
   section before it, so the border draws a stray rule across the container's
   top edge and the padding opens a gap under it.
   Stated once, generally, rather than as a list of containers to except. A
   layout probe over 18 pages found it in THREE places — the merge request's
   Checks route, the Actions workflow rail, the dashboard — and the first fix
   for it was scoped to .prd-panel and caught only one. The design system
   excepts specific containers by name (.ci-runs-head, .ci-run-head,
   .pr-list-head), which is the same rule written so that the next container
   has to be remembered; :first-child is the condition those names were
   approximating. TestLeadingSubheadDropsItsDivider. */
.settings-subhead:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.settings-empty { color: var(--fg-dim); font-size: .92rem; padding: 8px 0 16px; }
.mono-stat { font-family: var(--mono); font-size: .82rem; color: var(--fg-muted); margin: -4px 0 16px; }

.settings-help { color: var(--fg-dim); font-size: .9rem; margin: 0 0 14px; max-width: 60ch; }
.settings-list { list-style: none; margin: 0 0 8px; padding: 0; display: grid; gap: 8px; }
/* Audit log entries. */
.audit-list { list-style: none; margin: 0 0 20px; padding: 0; display: grid; gap: 6px; }
.audit-item {
  display: grid; grid-template-columns: max-content 1fr max-content; gap: 12px; align-items: baseline;
  padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev);
}
.audit-action { font: 600 .8rem var(--mono); color: var(--accent); white-space: nowrap; }
.audit-detail { color: var(--fg); overflow: hidden; text-overflow: ellipsis; }
.audit-meta { font: .8rem var(--mono); color: var(--fg-dim); white-space: nowrap; }
.audit-actor { color: var(--fg-muted); }
.settings-item {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev);
  transition: border-color .15s;
}
.settings-item:hover { border-color: var(--accent-line); }
.settings-item-main { display: grid; gap: 4px; min-width: 0; }
.settings-item-title { font-weight: 600; }
a.settings-item-title:hover { color: var(--accent); }
.settings-item-mono { font-family: var(--mono); font-size: .82rem; color: var(--fg-muted); word-break: break-all; }
.settings-item-meta { font-size: .82rem; color: var(--fg-dim); }
.settings-item-title .session-current { margin-left: 8px; padding: 2px 8px; border-radius: var(--pill); font-size: .68rem; font-weight: 600; background: color-mix(in oklab, var(--accent) 18%, var(--bg-elev)); color: var(--accent); border: 1px solid color-mix(in oklab, var(--accent) 40%, transparent); }
.session-ua { margin-top: 2px; font-size: .72rem; opacity: .8; }
.settings-body textarea.field-input { resize: vertical; font: inherit; }
.token-reveal {
  margin: 0 0 20px; padding: 14px 16px; border-radius: var(--radius);
  background: color-mix(in oklab, var(--accent) 12%, var(--bg-elev));
  border: 1px solid color-mix(in oklab, var(--accent) 45%, var(--line));
}
.token-reveal p { margin: 0 0 8px; font-size: .9rem; }
.token-value { display: block; font-family: var(--mono); font-size: .9rem; word-break: break-all; padding: 10px 12px; border-radius: var(--radius-sm); background: var(--bg); border: 1px solid var(--line); }

/* ---- two-factor auth ---- */
.tfa-status { display: flex; flex-direction: column; gap: 3px; margin: 0 0 20px;
  padding: 12px 16px; border-radius: var(--radius); border: 1px solid var(--line);
  background: var(--bg-elev); font-size: .9rem; }
.tfa-status span { color: var(--fg-dim); }
.tfa-status.tfa-on { border-color: color-mix(in oklab, var(--ok, #3fb950) 45%, var(--line));
  background: color-mix(in oklab, var(--ok, #3fb950) 10%, var(--bg-elev)); }
.tfa-qr { display: inline-block; margin: 8px 0 16px; padding: 12px; border-radius: var(--radius);
  background: #ffffff; border: 1px solid var(--line); line-height: 0; }
.tfa-secret { display: inline-block; margin: 4px 0 8px; letter-spacing: 1px; }
.tfa-warn { color: var(--danger, #f85149); }
.tfa-codes { list-style: none; margin: 0 0 20px; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 16px; }
.tfa-codes code { display: block; padding: 8px 10px; border-radius: var(--radius-sm);
  background: var(--bg); border: 1px solid var(--line); text-align: center; letter-spacing: 1px; }

/* ---- orgs & repos ---- */
/* The select chevron is part of .field-input, not of this page: it lives in
   components/forms.css beside the class it decorates. */
/* team tree: slugs are identifiers (mono); a faint elbow marks sub-team depth */
.teams-tree .settings-item-main { display: flex; flex-wrap: wrap; align-items: center; gap: 3px 8px; }
.teams-tree .team-slug { font-family: var(--mono); font-size: .88rem; letter-spacing: -.01em; }
.teams-tree .settings-item-meta { flex-basis: 100%; }
.team-guide { flex-shrink: 0; width: 10px; height: 9px; border-left: 1px solid var(--line); border-bottom: 1px solid var(--line); border-bottom-left-radius: 3px; margin-right: 2px; opacity: .85; }
/* CSP-safe team-tree indentation: depth classes (style-src 'self' blocks inline
   style, so an inline padding-left would silently collapse the hierarchy — see
   teamIndentClass). Clamped to team-indent-6 for deeper nesting. */
.team-indent-0 { padding-left: 0; }
.team-indent-1 { padding-left: 20px; }
.team-indent-2 { padding-left: 40px; }
.team-indent-3 { padding-left: 60px; }
.team-indent-4 { padding-left: 80px; }
.team-indent-5 { padding-left: 100px; }
.team-indent-6 { padding-left: 120px; }
.settings-item .inline-form + .inline-form { margin-left: 8px; }
.settings-item select.field-input { padding: 6px 10px; padding-right: 28px; font-size: .88rem; }
.danger-zone {
  margin-top: 12px; padding: 18px; border-radius: var(--radius);
  border: 1px solid color-mix(in oklab, var(--danger) 45%, var(--line));
  background: color-mix(in oklab, var(--danger) 7%, var(--bg-elev));
}
/* One .settings-item per destructive action, each with its consequence beside
   its own button. The rows sit ON the zone's tinted ground, so they drop the
   list's elevated fill and keep only a hairline. */
.danger-zone .settings-list { margin: 0; }
.danger-zone .settings-item {
  align-items: flex-start; background: transparent;
  border-color: color-mix(in oklab, var(--danger) 25%, var(--line));
}
.danger-zone .settings-item:hover { border-color: color-mix(in oklab, var(--danger) 45%, var(--line)); }
.danger-zone .settings-item-meta { color: var(--fg-muted); line-height: 1.5; max-width: 52ch; }
.danger-zone .inline-form { flex-shrink: 0; }
/* Three danger-zone rows need a FIELD above their button rather than a lone
   button: delete (type owner/name to confirm), rename (the new name) and
   transfer (the new owner). One class for all three — the layout is the same
   fact, and a second class name for it is the drift the DRY rule exists to
   prevent. Below the two-column breakpoint the form wraps under the copy
   instead of squeezing the field to nothing. */
.danger-zone .danger-confirm { display: grid; gap: 8px; margin: 0; min-width: 15rem; }
.danger-zone .danger-confirm .field { margin: 0; }
@media (max-width: 40rem) {
  .danger-zone .settings-item { flex-wrap: wrap; }
  .danger-zone .danger-confirm { width: 100%; }
}

/* #18 — one-time token reveal: value + copy button on one row, like the clone box. */
.token-reveal-row { display: flex; align-items: center; gap: 10px; }
.token-reveal-row .token-value { flex: 1; min-width: 0; overflow-x: auto; }


/* #100 — CI-minute usage on the repo's Actions settings page. The meter IS the
   item's subject here (not a small figure beside a label as on the runs list), so
   it spans the row rather than borrowing .ci-usage's 140px inline width. */
.settings-usage .settings-item-main { width: 100%; }
.settings-usage .milestone-bar { width: 100%; max-width: none; margin-top: 2px; }
}

/* ==== css/pages/admin.css ==== */
@layer pages {
/* ---- waitlist moderation: pure-CSS tabs + tables (Pending/Validated/Refused) ---- */
.wl { margin-top: 6px; }
.wl-radio { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.wl-tabbar { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin-bottom: 18px; flex-wrap: wrap; }
.wl-tab {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  padding: 9px 15px; margin-bottom: -1px; border: 1px solid transparent; border-bottom: none;
  border-radius: 9px 9px 0 0; color: var(--fg-muted); font-size: .9rem; font-weight: 600;
}
.wl-tab:hover { color: var(--fg); }
.wl:has(#wl-pending:checked) .wl-tab[for="wl-pending"],
.wl:has(#wl-unconfirmed:checked) .wl-tab[for="wl-unconfirmed"],
.wl:has(#wl-validated:checked) .wl-tab[for="wl-validated"],
.wl:has(#wl-refused:checked) .wl-tab[for="wl-refused"] {
  color: var(--fg); background: var(--bg-elev); border-color: var(--line); border-bottom-color: var(--bg-elev);
}
.wl:has(#wl-pending:focus-visible) .wl-tab[for="wl-pending"],
.wl:has(#wl-unconfirmed:focus-visible) .wl-tab[for="wl-unconfirmed"],
.wl:has(#wl-validated:focus-visible) .wl-tab[for="wl-validated"],
.wl:has(#wl-refused:focus-visible) .wl-tab[for="wl-refused"] {
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent);
}
.wl-panel { display: none; }
.wl:has(#wl-pending:checked) .wl-panel-pending,
.wl:has(#wl-unconfirmed:checked) .wl-panel-unconfirmed,
.wl:has(#wl-validated:checked) .wl-panel-validated,
.wl:has(#wl-refused:checked) .wl-panel-refused { display: block; }

.wl-table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); }
.wl-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.wl-table th {
  text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line);
  font: 700 .66rem var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--fg-dim); white-space: nowrap;
}
.wl-table td { padding: 11px 14px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
.wl-table tr:last-child td { border-bottom: 0; }
.wl-table tbody tr:hover { background: var(--bg-elev-2); }
.wl-handle { font-family: var(--mono); font-weight: 600; color: var(--fg); white-space: nowrap; }
.wl-email { font-family: var(--mono); font-size: .82rem; color: var(--fg-muted); max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wl-joined { font-family: var(--mono); font-size: .8rem; color: var(--fg-dim); white-space: nowrap; }
.wl-actions-col { width: 1%; }
.wl-actions { display: flex; gap: 8px; justify-content: flex-end; white-space: nowrap; }
.wl-actions .btn { padding: 6px 13px; font-size: .82rem; }

/* Feature-flag console (superadmin): one card per flag, a state control, and the
   per-subject override list + add form. Reuses the design tokens (mono voice,
   portal-green accent, danger tint) so it reads as one system with billing/usage. */
.flag-card {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; margin: 14px 0; background: var(--bg-elev);
}
.flag-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.flag-key { font: 700 var(--text-sm) var(--mono); color: var(--accent); }
.flag-desc { color: var(--fg-muted); font-size: var(--text-sm); margin: 6px 0 10px; }
.flag-hint { color: var(--fg-dim); font-size: var(--text-sm); margin: 4px 0 10px; }
.flag-warn {
  color: var(--danger); font-size: var(--text-sm); margin: 4px 0 10px;
  border: 1px solid color-mix(in oklab, var(--danger) 45%, var(--line));
  background: color-mix(in oklab, var(--danger) 8%, var(--bg-elev));
  border-radius: var(--radius-sm); padding: 8px 12px;
}
.chip {
  font: 600 var(--text-xs)/1 var(--sans); text-transform: uppercase; letter-spacing: .04em;
  padding: 4px 9px; border-radius: var(--pill); border: 1px solid var(--line);
  color: var(--fg-muted); background: var(--bg-elev-2);
}
.chip-warn { color: var(--warn); border-color: color-mix(in oklab, var(--warn) 40%, var(--line)); }
.chip-on { color: var(--success); border-color: var(--success-line); background: var(--success-soft); }
.chip-off { color: var(--fg-dim); }
.flag-state-form {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  padding: 12px 0; border-top: 1px solid var(--line);
}
.flag-field { display: flex; flex-direction: column; gap: 4px; }
.flag-label { font-size: var(--text-xs); color: var(--fg-dim); text-transform: uppercase; letter-spacing: .06em; }
.flag-select, .flag-pct, .flag-handle {
  font-family: var(--mono); padding: 7px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--bg-elev-2); color: var(--fg);
}
.flag-pct { width: 6.5rem; }
.flag-statehelp { color: var(--fg-dim); font-size: var(--text-sm); align-self: center; }
.flag-overrides { border-top: 1px solid var(--line); padding-top: 12px; }
.flag-oh { font-size: var(--text-sm); text-transform: uppercase; letter-spacing: .06em; color: var(--fg-dim); margin: 0 0 8px; }
.flag-ovlist { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.flag-ovrow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.flag-ovsubj { font-family: var(--mono); }
.flag-addov { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.flag-subjtype { display: flex; gap: 12px; border: 0; padding: 0; margin: 0; }
.flag-subjtype label, .flag-forceon { font-size: var(--text-sm); color: var(--fg-muted); display: inline-flex; align-items: center; gap: 5px; }

/* Runner-capacity dashboard (superadmin) --------------------------------- */
.cap-verdict {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-elev); padding: 18px 20px; margin: 8px 0 24px;
  border-left-width: 4px;
}
.cap-verdict.cap-healthy { border-left-color: var(--success); }
.cap-verdict.cap-warning { border-left-color: #ffd166; }
.cap-verdict.cap-act     { border-left-color: var(--danger); }
.cap-verdict-head { display: flex; align-items: center; gap: 10px; }
.cap-verdict-dot { width: 12px; height: 12px; border-radius: 50%; flex: none; background: var(--fg-dim); }
.cap-healthy .cap-verdict-dot { background: var(--success); box-shadow: 0 0 0 4px var(--success-soft); }
.cap-warning .cap-verdict-dot { background: var(--warn); box-shadow: 0 0 0 4px color-mix(in oklab, var(--warn) 22%, transparent); }
.cap-act .cap-verdict-dot { background: var(--danger); box-shadow: 0 0 0 4px color-mix(in oklab, var(--danger) 22%, transparent); }
.cap-verdict-title { font-weight: 700; font-size: 1.06rem; letter-spacing: -.01em; }
.cap-verdict-reasons { margin: 12px 0 0; padding-left: 20px; color: var(--fg-muted); font-size: .92rem; }
.cap-verdict-reasons li { margin: 4px 0; }
.cap-verdict-none { margin: 10px 0 0; color: var(--fg-muted); font-size: .92rem; }

/* KPI tiles reflow by available width (container query) — see cap widgets below. */
.cap-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: 14px; margin: 8px 0 4px; }
.cap-tile {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--bg-elev); padding: 14px 16px;
}
.cap-tile-label { font: 700 .66rem var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--fg-dim); }
.cap-tile-value { font-size: 1.5rem; font-weight: 700; letter-spacing: -.02em; margin: 6px 0 2px; color: var(--fg); }
.cap-tile-sub { font-size: .82rem; color: var(--fg-muted); }

.cap-meter { height: 8px; border-radius: var(--pill); background: var(--bg-elev-2); overflow: hidden; margin: 8px 0; }
.cap-meter-fill { display: block; width: 0; height: 100%; border-radius: var(--pill);
  background: linear-gradient(90deg, var(--accent-2), var(--accent)); }
/* CSP-safe fill widths: the strict CSP (style-src 'self') blocks inline
   style="width:…", so the fill fraction is carried by a class snapped to 5%
   buckets (see components.capFillClass), same convention as .usage-fill-*
   in billing.css. Bucketing to 5% is intentional — "roughly how full", not a
   pixel-accurate reading. */
.cap-fill-0 { width: 0; }
.cap-fill-5 { width: 5%; }
.cap-fill-10 { width: 10%; }
.cap-fill-15 { width: 15%; }
.cap-fill-20 { width: 20%; }
.cap-fill-25 { width: 25%; }
.cap-fill-30 { width: 30%; }
.cap-fill-35 { width: 35%; }
.cap-fill-40 { width: 40%; }
.cap-fill-45 { width: 45%; }
.cap-fill-50 { width: 50%; }
.cap-fill-55 { width: 55%; }
.cap-fill-60 { width: 60%; }
.cap-fill-65 { width: 65%; }
.cap-fill-70 { width: 70%; }
.cap-fill-75 { width: 75%; }
.cap-fill-80 { width: 80%; }
.cap-fill-85 { width: 85%; }
.cap-fill-90 { width: 90%; }
.cap-fill-95 { width: 95%; }
.cap-fill-100 { width: 100%; }

.cap-chart {
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev);
  padding: 16px; margin: 4px 0 8px;
}
.cap-chart-bars { display: flex; align-items: flex-end; gap: 2px; height: 120px; }
.cap-bar { flex: 1 1 0; min-width: 2px; background: var(--accent-line); height: 0;
  border-radius: 2px 2px 0 0; min-height: 1px; transition: background .15s; }
.cap-bar:hover { background: var(--accent); }
/* CSP-safe bar heights for the queue-depth chart, same convention and 5%
   bucket granularity as .cap-fill-* above (see components.capBarHeightClass). */
.cap-bar-h-0 { height: 0; }
.cap-bar-h-5 { height: 5%; }
.cap-bar-h-10 { height: 10%; }
.cap-bar-h-15 { height: 15%; }
.cap-bar-h-20 { height: 20%; }
.cap-bar-h-25 { height: 25%; }
.cap-bar-h-30 { height: 30%; }
.cap-bar-h-35 { height: 35%; }
.cap-bar-h-40 { height: 40%; }
.cap-bar-h-45 { height: 45%; }
.cap-bar-h-50 { height: 50%; }
.cap-bar-h-55 { height: 55%; }
.cap-bar-h-60 { height: 60%; }
.cap-bar-h-65 { height: 65%; }
.cap-bar-h-70 { height: 70%; }
.cap-bar-h-75 { height: 75%; }
.cap-bar-h-80 { height: 80%; }
.cap-bar-h-85 { height: 85%; }
.cap-bar-h-90 { height: 90%; }
.cap-bar-h-95 { height: 95%; }
.cap-bar-h-100 { height: 100%; }
.cap-chart-cap { margin: 10px 0 0; font-size: .82rem; color: var(--fg-muted); font-family: var(--mono); }

.cap-table { width: 100%; border-collapse: collapse; font-size: .92rem;
  border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; margin: 4px 0 8px; }
.cap-table th { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line);
  font: 700 .66rem var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--fg-dim); background: var(--bg-elev); }
.cap-table td { padding: 11px 14px; border-bottom: 1px solid var(--line-soft); color: var(--fg-muted); }
.cap-table tr:last-child td { border-bottom: 0; }
.cap-num { font-family: var(--mono); color: var(--fg); text-align: right; }
.cap-th-note { display: block; font-size: .76rem; color: var(--fg-dim); font-weight: 400; }

.cap-panel { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); padding: 16px 18px; margin: 4px 0 8px; }
.cap-panel-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 6px 0; }
.cap-panel-label { color: var(--fg-muted); font-size: .9rem; }
.cap-panel-value { color: var(--fg); font-weight: 600; text-align: right; }
.cap-panel-note { margin: 8px 0 0; font-size: .82rem; color: var(--fg-dim); }
.cap-leak { margin: 12px 0 0; padding: 10px 12px; border-radius: var(--radius-sm);
  background: color-mix(in oklab, var(--danger) 12%, transparent);
  border: 1px solid color-mix(in oklab, var(--danger) 40%, var(--line)); color: var(--fg); font-size: .88rem; }

/* Fleet hosts: one .cap-node panel per reporting node (or the local-probe
   fallback). Provenance is part of the design here — this panel used to show
   the web process's own container while claiming to describe the fleet host,
   so where the numbers came from is stated above the rows, and a node whose
   heartbeat has gone quiet is visibly dimmed rather than silently frozen. */
.cap-source { margin: 4px 0 10px; font-size: .82rem; color: var(--fg-dim); font-family: var(--mono); }
.cap-node-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; }
.cap-node-name { font-weight: 700; letter-spacing: -.01em; color: var(--fg); }
.cap-node-chip { font: 600 .7rem var(--mono); letter-spacing: .04em; text-transform: uppercase;
  color: var(--fg-dim); border: 1px solid var(--line); border-radius: var(--pill); padding: 2px 8px; }
.cap-node-flag { margin-left: auto; }
.cap-node-status { margin: 6px 0 10px; font-size: .8rem; color: var(--fg-muted); }
.cap-node-stale, .cap-node-noheartbeat { border-style: dashed; }
.cap-node-stale .cap-panel-value,
.cap-node-stale .cap-node-name { color: var(--fg-muted); }
.cap-node-stale .cap-meter-fill { filter: grayscale(1); }
}

/* ==== css/pages/pr.css ==== */
@layer pages {
/* ---- pull requests ---- */
/* .pr-list-head / .pr-list-actions / .pr-tabs / .pr-tab all moved to
   components/list-filters.css: the row is rendered by the shared listPageHead
   and hand-written on the commits, releases, tags and labels pages besides, so
   seven surfaces depend on it and none of them is "the pull request page".

   This ONE rule stayed behind, and has to. .settings-subhead is a SECTION
   divider: border-top plus 24px of padding above its text. As a flex child of
   the head row it is content-width, so that border renders as a ~190px stub
   floating above the heading, and the padding drops the heading off the
   actions' baseline (visible on the commits and releases pages). Here the
   heading titles a row, not a section — the row's own margin does the
   separating, so the divider chrome comes off.

   It cannot follow .pr-list-head into `components`, because what it overrides
   (.settings-subhead, pages/settings.css:20) is in `pages`, and @layer order
   beats specificity outright: from the components layer this rule would lose
   and every list head in the app would grow its divider back. An override
   belongs in the layer of the rule it overrides — see #160 for the root cause,
   which is that .settings-subhead is shared chrome living in a page file. */
.pr-list-head > .settings-subhead { margin: 0; padding-top: 0; border-top: 0; }

.pr-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.pr-item { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; border-top: 1px solid var(--line-soft); transition: background .12s; }
.pr-item:first-child { border-top: none; }
.pr-item:hover { background: var(--bg-elev-2); }
/* The state chip is a fixed COLUMN, not content-sized: "Open", "Merged" and
   "Closed" are three different widths, so letting the chip size itself made
   every title in the list start at a different x. Fixing the column at 78px
   (the widest chip plus its padding) is what makes a list of mixed states read
   as a list. Applies to issues and merge requests alike — same component. */
.pr-item > .pr-state { flex: none; min-width: 78px; justify-content: center; }

.pr-item-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pr-item-title { font-weight: 600; color: var(--fg); }
.pr-item-title:hover { color: var(--accent); }
.pr-item-meta { font: .8rem var(--mono); color: var(--fg-dim); }
/* The row's SECOND meta line: an open MR's head-commit CI verdict, or a merged /
   closed MR's outcome. The pill is the shared .ci-status badge, so a verdict
   looks identical here, on the Checks tab and on the Actions pages; the wording
   beside it is prose, so it drops the meta line's mono face. */
.pr-item-status { display: flex; align-items: center; gap: 8px; }
.pr-item-status > span:last-child { font: 500 .8rem var(--sans); }
/* .pr-empty moved to components/empty-states.css — three pages render it
   (merge requests, issues, filtered CI runs), so it is a component. */

.pr-state { display: inline-flex; align-items: center; gap: 5px; padding: 3px 11px; border-radius: var(--pill); font-size: .72rem; font-weight: 600; letter-spacing: .02em; white-space: nowrap; }
.pr-state::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.pr-state-open { background: var(--success-soft); color: var(--success); border: 1px solid color-mix(in oklab, var(--success) 40%, transparent); }
.pr-state-merged { background: color-mix(in oklab, var(--merge) 20%, var(--bg-elev)); color: var(--merge); border: 1px solid color-mix(in oklab, var(--merge) 45%, transparent); }
.pr-state-closed { background: color-mix(in oklab, var(--danger) 16%, var(--bg-elev)); color: var(--danger); border: 1px solid color-mix(in oklab, var(--danger) 40%, transparent); }
.pr-state-draft { background: var(--bg-elev-2); color: var(--fg-muted); border: 1px solid var(--line); }

.pr-draft-control { display: flex; align-items: center; gap: 10px; margin: 12px 0; flex-wrap: wrap; }

.pr-branch-row { display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; }
.pr-branch-row .field { flex: 1 1 220px; }
.pr-branch-arrow { padding-bottom: 12px; color: var(--fg-dim); font-size: 1.1rem; }

.pr-detail-head { margin-bottom: 8px; }
/* NOT mono. 726359c put `font-family: var(--mono); font-weight: 650` here, on
   the stated grounds that primary page titles carry "the monospace display
   voice ... matching the mono labels/eyebrows already in the system". That
   reason describes the design system as the commit imagined it, not as it is:
   the design system's own `.pr-detail-title` is `font-weight: 600;
   letter-spacing: -.02em` and sets NO font-family, so it inherits the body
   sans — which is the whole point. An issue or merge-request title is the
   author's PROSE, the longest piece of running text on the page after the
   description; the mono voice is for identifiers, and it is why every title in
   the app read wrong. The identifiers around it keep it: the "#7" in
   .pr-detail-num below, the branch names in .pr-detail-sub code — which is
   exactly the distinction the override erased. That a pages/ file can
   override a design-system
   rule with nothing detecting it is the root cause, and it is #164. The same
   commit's other mono title (.settings-title) came off for the same reason once
   the design system's rule for it could be read — #165, closed; .dash-title,
   90e4d9b's, went with it. All three read the same and were wrong the same way.
   NOTE the weights differ on purpose: the design system kept 726359c's 650 on
   .settings-title and 600 here, so "make the titles match each other" is not
   the rule — "read the design system's rule for THIS selector" is. */
.pr-detail-title { font-weight: 600; font-size: 1.5rem; margin: 0 0 8px; letter-spacing: -.02em; }
.pr-detail-num { color: var(--fg-dim); font-weight: 400; font-family: var(--mono); }
.pr-detail-sub { display: flex; align-items: center; gap: 10px; color: var(--fg-muted); font-size: .9rem; flex-wrap: wrap; }
.pr-detail-sub code, .pr-compare-summary code { font-family: var(--mono); font-size: .82rem; padding: 1px 6px; border-radius: 6px; background: var(--bg-elev-2); border: 1px solid var(--line); }

.pr-actions { display: flex; align-items: center; gap: 10px; margin: 16px 0; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); flex-wrap: wrap; }
.pr-merge-form { display: flex; align-items: center; gap: 10px; }
.pr-merge-form select.field-input { padding: 8px 10px; padding-right: 28px; font-size: .85rem; }
.pr-compare-summary { margin: 16px 0 10px; color: var(--fg-muted); font: .84rem var(--mono); }
.pr-compare-form { margin-bottom: 8px; }
.pr-compare-preview { margin: 8px 0 28px; }
.pr-compare-preview .commit-list { margin-bottom: 20px; }

/* .pr-comments / .pr-comment* moved to components/comment-thread.css: the
   thread is one shared component rendered by both detail pages, and its CSS
   had drifted into two page files (a second .pr-comment-head lived in
   pages/issues.css). Same rule as .pr-tabs — markup in components/, CSS in
   components/, where a page file cannot silently outrank it. */

/* ---- MR detail: sub-tab ROUTES + reviews sidebar ----
   The four sections (Conversation / Commits / Files changed / Checks) are real
   routes, so the tab bar is links and the page renders exactly one panel. The
   current tab is marked by the server with .prd-tab-cur — the design system's
   own tab pattern is radio-driven, so this class exists precisely for
   link-based tabs — and by aria-current="page". */
.prd { margin-top: 4px; }
.prd-tabbar { display: flex; gap: 2px; border-bottom: 1px solid var(--line); margin: 16px 0 22px; flex-wrap: wrap; }
.prd-tab {
  display: inline-flex; align-items: center; gap: 7px; cursor: pointer; text-decoration: none;
  padding: 9px 15px; margin-bottom: -1px; border: 1px solid transparent; border-bottom: none;
  border-radius: 9px 9px 0 0; color: var(--fg-muted); font-size: .9rem; font-weight: 600;
}
.prd-tab:hover { color: var(--fg); }
.prd-tab-cur {
  color: var(--fg); background: var(--bg-elev); border-color: var(--line); border-bottom-color: var(--bg-elev);
}
.prd-tab:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent);
}
/* One panel per route — the page never carries the sections it isn't. */
.prd-panel { display: block; }

/* Layout: the reviews sidebar (reviewers/assignees/labels) belongs to the
   Conversation route ONLY — Commits, Files changed and Checks render full-width
   so their content (notably split diffs) isn't cramped. The other routes don't
   render .prd-side at all, so the grid keys off its presence rather than off a
   tab-state selector. */
.prd-cols { display: grid; grid-template-columns: 1fr; gap: 28px; align-items: start; }
.prd-main { min-width: 0; }
.prd-side { display: flex; flex-direction: column; gap: 16px; }
/* Issue detail uses THIS sidebar too, not a class of its own.
   History, because the comment that used to sit here warned against exactly
   this: .prd-side was once `display: none` outside the PR conversation tab, so
   reusing it on issues hid the whole triage sidebar — labels, milestone,
   assignees. That rule is gone. The tabs became real routes (pull_tabs.go), so
   a route that has no sidebar simply does not RENDER .prd-side, and the layout
   below keys off its presence rather than hiding it after the fact. Nothing in
   this stylesheet sets `display: none` on it any more —
   TestPrdSideIsNeverHidden holds that, so the old bug cannot come back by
   accident under a class the issue page silently depends on.
   With the rule gone, .issue-side was a byte-for-byte copy of the three rules
   below under a different name — and because the two-column grid keys off
   `:has(> .prd-side)`, the copy is what kept the issue page ONE column at every
   width (#120): the sidebar was styled, but the grid never saw it. */
@media (min-width: 57rem) {
  .prd-cols:has(> .prd-side) { grid-template-columns: minmax(0, 1fr) 280px; }
}

/* sidebar subject/review rows inside .side-panel cards */
.side-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.side-subject, .side-review { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: .88rem; }
.side-subject-name { font-weight: 600; }
.side-subject-meta { color: var(--fg-dim); font-size: .8rem; }
.side-review-when { color: var(--fg-dim); font-size: .76rem; font-family: var(--mono); margin-left: auto; }
.prd-side .inline-form, .prd-side .side-form { margin-top: 10px; }
.prd-side .inline-form { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---- sidebar meta panels: labels / assignees / milestone editing ----
   Ported verbatim from the design system (css/pages/pr.css) when it promoted
   these out of the Pages project's app.css. .side-form is a stacked editing form
   inside a .side-panel card: children go full width, buttons get a little air,
   and a two-up row (input + button) uses .pr-list-actions. Assignees render as a
   compact avatar row with a hover/focus name tooltip and a remove badge —
   previously a text list with a full "Remove" button per row, which is why three
   assignees filled the card. */
.side-panel .side-form { display: grid; gap: 10px; }
.side-panel .side-form .field-input,
.side-panel .side-form .c-select,
.side-panel .side-form .seg { width: 100%; }
.side-panel .side-form > .btn,
.side-panel .side-form > .pr-list-actions { margin-top: 4px; }
.side-panel .side-form .pr-list-actions { display: grid; grid-template-columns: 1fr auto; gap: 8px; }
.side-avatars { display: flex; flex-wrap: wrap; gap: 10px; margin: 2px 0 4px; }
.side-assignee { position: relative; display: inline-flex; }
.side-assignee-name {
	position: absolute; bottom: calc(100% + 7px); left: 50%; transform: translateX(-50%);
	padding: 3px 8px; border-radius: var(--radius-sm); white-space: nowrap;
	background: var(--fg); color: var(--bg); font-size: .74rem; font-weight: 600;
	opacity: 0; pointer-events: none; transition: opacity .12s; z-index: 2;
}
.side-assignee:hover .side-assignee-name,
.side-assignee:focus-within .side-assignee-name { opacity: 1; }
.side-assignee-remove {
	position: absolute; top: -5px; right: -5px; width: 18px; height: 18px; padding: 0;
	display: grid; place-items: center; cursor: pointer;
	border: 1px solid var(--line); border-radius: 50%;
	background: var(--bg-elev); color: var(--danger); font-size: .66rem; line-height: 1;
	opacity: .45; transition: opacity .12s;
}
.side-assignee:hover .side-assignee-remove,
.side-assignee-remove:focus-visible { opacity: 1; }
/* A touch device has no hover, so the badge would never appear. */
@media (hover: none) { .side-assignee-remove { opacity: 1; } }
.side-assignee-remove:hover { border-color: var(--danger); background: color-mix(in oklab, var(--danger) 12%, var(--bg-elev)); }
.side-empty { margin: 0; color: var(--fg-dim); font-size: .86rem; }
/* Tighter pager variant inside a panel that follows a list. */
.side-pager-tight { margin-top: 6px; }
.side-pager a { margin: 0; }

/* ---- Athena AI-review panel (gated by the `athena` flag) ----
   Reuses the .side-panel card; adds the AI badge, a status line, the summary
   block and a compact transparency (model/tokens) list. */
.athena-panel .side-panel-head { display: flex; align-items: center; gap: 8px; }
.athena-badge { font-size: .66rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
	padding: 2px 6px; border-radius: 999px; background: var(--accent-2); color: #fff; }
.athena-status { margin: 0 0 8px; font-size: .84rem; font-weight: 600; }
.athena-status-running { color: var(--accent-2); }
.athena-status-done { color: var(--ok, #2e7d32); }
.athena-status-error { color: var(--danger, #c62828); }
.athena-summary { font-size: .86rem; line-height: 1.55; margin: 0 0 10px; }
.athena-meta { list-style: none; margin: 0 0 4px; padding: 0; display: flex; flex-direction: column; gap: 4px;
	font-size: .78rem; color: var(--fg-dim); }
}

/* ==== css/pages/diff.css ==== */
@layer pages {
/* ---- diff ---- */
/* CLIP, NOT HIDDEN — and this is load-bearing, not a synonym.
   overflow:hidden makes an element a SCROLL CONTAINER, and a scroll container is
   the scrollport its sticky descendants position against. .diff-file-head is
   `position: sticky; top: 62px` (62px = the site header it must clear), so with
   overflow:hidden the head measured that 62px against .diff-file itself: a box
   that never scrolls. The header therefore never stuck to anything AND was shoved
   62px down from the card's top edge, sitting on top of the first two rows of
   every diff on the page — measured at 522px against a card top of 459px.
   overflow:clip clips the rounded corners identically without creating a scroll
   container, so the head lands at the card's top edge (460px) and sticks to the
   VIEWPORT as intended.
   The design system carries `overflow: hidden` here too, so this is a design bug
   we fix rather than a divergence we adopt — see docs/design-sync.md.
   TestStickyDiffHeaderIsNotTrappedInAScrollContainer. */
.diff-file { border: 1px solid var(--line); border-radius: var(--radius); overflow: clip; margin-bottom: 16px; }
.diff-file-head { display: flex; align-items: center; gap: 10px; padding: 9px 14px; background: var(--bg-elev-2); border-bottom: 1px solid var(--line-soft); font-size: .85rem; position: sticky; top: 62px; z-index: 1; }
.diff-path { font-family: var(--mono); color: var(--fg); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Status is a full word ("added"/"modified"/"deleted"/"renamed") — size the badge
   to its content (flex-shrink:0 so it never collapses onto the path). */
.diff-status {
  display: inline-block; flex-shrink: 0; padding: 1px 8px; border-radius: var(--radius-sm);
  font-family: var(--sans); font-size: .68rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .04em; white-space: nowrap;
  background: var(--bg); border: 1px solid var(--line); color: var(--fg-muted);
}
.diff-status-added { color: var(--success); border-color: var(--success-line); }
.diff-status-deleted { color: var(--danger); border-color: color-mix(in oklab, var(--danger) 40%, var(--line)); }
.diff-status-renamed { color: var(--accent-2); border-color: color-mix(in oklab, var(--accent-2) 40%, var(--line)); }
.diff-add { color: var(--success); font-family: var(--mono); font-size: .8rem; font-weight: 600; }
.diff-del { color: var(--danger); font-family: var(--mono); font-size: .8rem; font-weight: 600; }
.diff-empty { padding: 12px 14px; color: var(--fg-dim); font-size: .85rem; }
/* Collapsed (lazy-loaded) file: a "Load diff" button in the head + a note. The
   button fetches the body on demand so big MRs stay light. */
.diff-load { margin-left: 4px; white-space: nowrap; }
.diff-file.htmx-request .diff-load { opacity: .55; pointer-events: none; }
.diff-collapsed-note { padding: 12px 14px; color: var(--fg-dim); font-size: .85rem; font-style: italic; }
/* While the diff is in flight the skeleton stands in the note's place, so the
   two never stack. htmx drops .htmx-request on every terminal path (including a
   500), which brings the note back — the failure path must not leave a file
   looking permanently blank. The skeleton needs the file's own padding, which
   the note carries and the shared .sk does not. */
.diff-file.htmx-request .diff-collapsed-idle { display: none; }
.diff-file .sk { padding: 12px 14px; }

/* ── Files-changed browser: file tree + collapsible diffs + scroll-spy ──────
   Two columns — a sticky left tree that navigates and (via files-changed.js)
   highlights the file under review, beside the per-file diffs. Directory
   indentation is structural (nested .fc-subtree), never inline styles. */
.fc-layout { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 22px; align-items: start; }
.fc-main { min-width: 0; }
.fc-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.fc-toolbar .spacer { flex: 1; }
.fc-btn { font-size: .8rem; padding: 5px 11px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--bg-elev); color: var(--fg); cursor: pointer; }
.fc-btn:hover { background: var(--bg-elev-2); }
.fc-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent); }

.fc-tree { position: sticky; top: 62px; max-height: calc(100vh - 82px); overflow: auto;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); }
.fc-tree-head { position: sticky; top: 0; z-index: 1; background: var(--bg-elev-2);
  border-bottom: 1px solid var(--line-soft); padding: 9px 12px; font-size: .72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--fg-muted); }
.fc-tree-list, .fc-subtree { list-style: none; margin: 0; padding: 0; }
.fc-subtree { padding-left: 13px; }            /* structural indentation per level */
.fc-dir-row, .fc-file-row { display: flex; align-items: center; gap: 6px; width: 100%; text-align: left;
  padding: 4px 10px; font-size: .82rem; border: none; background: none; cursor: pointer; color: var(--fg);
  text-decoration: none; border-left: 2px solid transparent; }
.fc-dir-row { color: var(--fg-muted); font-weight: 600; }
.fc-dir-row:hover, .fc-file-row:hover { background: var(--bg-elev-2); }
.fc-dir-row:focus-visible, .fc-file-row:focus-visible { outline: none; background: var(--bg-elev-2);
  box-shadow: inset 2px 0 0 var(--accent); }
.fc-caret { font-size: .7rem; width: .8em; flex-shrink: 0; color: var(--fg-dim); transition: transform .12s ease; }
.fc-dir-collapsed > .fc-dir-row .fc-caret { transform: rotate(-90deg); }
.fc-dir-collapsed > .fc-subtree { display: none; }
.fc-file-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--mono); font-size: .78rem; }
.fc-stat { margin-left: auto; font-size: .68rem; white-space: nowrap; padding-left: 8px; font-variant-numeric: tabular-nums; }
.fc-add { color: var(--success); } .fc-del { color: var(--danger); }
.fc-dot { width: 8px; height: 8px; border-radius: 2px; flex-shrink: 0; background: var(--fg-dim); }
.fc-dot-added { background: var(--success); }
.fc-dot-modified { background: #d4a72c; }
.fc-dot-deleted { background: var(--danger); }
.fc-dot-renamed { background: var(--accent-2); }
/* scroll-spy: the file currently under review */
.fc-file-row.fc-active { background: var(--accent-soft); border-left-color: var(--accent);
  color: var(--fg); font-weight: 600; }

/* per-file fold */
.fc-collapse { background: none; border: none; cursor: pointer; padding: 0 2px; margin-left: -2px;
  color: var(--fg-dim); display: inline-flex; align-items: center; }
.fc-collapse:focus-visible { outline: none; box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent); border-radius: 4px; }
.fc-collapse .fc-caret { transition: transform .12s ease; }
.diff-file.fc-collapsed .fc-collapse .fc-caret { transform: rotate(-90deg); }
.diff-file.fc-collapsed .diff-file-body { display: none; }
.diff-file { scroll-margin-top: 66px; }        /* clear the sticky header on anchor scroll */

@media (max-width: 60rem) {
  .fc-layout { grid-template-columns: 1fr; }
  .fc-tree { position: static; max-height: 320px; }
}

/* Unified/Split layout toggle: pure CSS via visually-hidden radios + :has(),
   mirroring the clone HTTPS/SSH toggle. A single radiogroup switches every file
   at once; both layouts' markup is always present and the inactive one hidden. */
.diff-mode-toggle { display: inline-flex; gap: 4px; margin-bottom: 12px; }
.diff-mode {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.diff-tab {
  cursor: pointer; user-select: none; font-size: .72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--fg-muted);
  padding: 4px 12px; border: 1px solid var(--line); border-radius: 6px; background: var(--bg-elev);
}
.diff-mode:checked + .diff-tab { color: var(--accent); background: var(--accent-soft); border-color: var(--accent); }
.diff-mode:focus-visible + .diff-tab { box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent); }
/* Default Unified: hide the split layout until Split is checked, and vice versa. */
.diff-view:has(.diff-mode-unified:checked) .diff-split { display: none; }
.diff-view:has(.diff-mode-split:checked) .diff-unified { display: none; }

/* Diff body: each layout scrolls horizontally within itself so long lines never
   push the page sideways. */
.diff-unified, .diff-split { overflow-x: auto; background: var(--bg); }
.diff-table {
  border-collapse: collapse; width: 100%;
  font-family: var(--mono); font-size: .82rem; line-height: 1.5;
}
.diff-row { vertical-align: top; }
.diff-gutter {
  width: 1%; white-space: nowrap; text-align: right; padding: 0 8px;
  color: var(--fg-dim); background: var(--bg-elev); border-right: 1px solid var(--line-soft);
  user-select: none; -webkit-user-select: none;
}
.diff-code { white-space: pre; padding: 0 12px; width: 100%; border-left: 3px solid transparent; }
.diff-table-split .diff-code { width: 50%; }
/* Line coloring: whole row (unified) or the individual side cell (split). */
.diff-row-add .diff-code, .diff-side-add {
  background: color-mix(in oklab, var(--success) 15%, transparent);
  border-left-color: color-mix(in oklab, var(--success) 60%, transparent);
}
.diff-row-del .diff-code, .diff-side-del {
  background: color-mix(in oklab, var(--danger) 12%, transparent);
  border-left-color: color-mix(in oklab, var(--danger) 55%, transparent);
}
.diff-row-hunk { background: color-mix(in oklab, var(--accent-2) 12%, transparent); }
.diff-row-hunk .diff-code { color: var(--accent-2); }
.diff-row-meta .diff-code { color: var(--fg-dim); }
.diff-blank { background: color-mix(in oklab, var(--fg) 3%, transparent); }

/* Syntax-highlighted diff lines: the leading +/-/space marker stays plain, and
   the Chroma-classed code sits in a .diff-chroma wrapper whose own background and
   base colour are neutralised so the add/delete/context tint shows through and the
   line colour follows the theme; only the Chroma token colours apply on top. */
.diff-marker { user-select: none; -webkit-user-select: none; }
.diff-code .diff-chroma { background: transparent; color: inherit; }


/* Hover affordance: a "+" that appears at the left edge of a code line on row
   hover (or keyboard focus), CSP-safe (pure CSS, no inline JS). */
.diff-code { position: relative; }
.inline-add-btn {
  position: absolute; left: -13px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; padding: 0; line-height: 1; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--accent); border-radius: 5px;
  background: var(--accent); color: var(--accent-ink);
  font-weight: 700; cursor: pointer; opacity: 0; transition: opacity .1s ease;
}
.diff-row:hover .inline-add-btn,
.inline-add-btn:focus-visible { opacity: 1; }

/* The comment row spans the diff and hosts the thread(s) for one line. */
.diff-comment-row > .diff-comment-cell { padding: 0; background: var(--bg-elev); }
.inline-anchor {
  display: flex; flex-direction: column; gap: 10px;
  padding: 12px 14px 12px 40px; border-left: 3px solid var(--accent-2);
  font-family: var(--sans, inherit); white-space: normal;
}

.inline-thread {
  display: flex; flex-direction: column; gap: 8px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); padding: 10px 12px; max-width: 760px;
}
.inline-thread.resolved { opacity: .72; }
/* Resolving/reopening replaces the whole row, so the thread goes busy rather
   than growing a skeleton beside content it is about to replace. Like every
   indicator here it is a class htmx owns, so it lifts on failure too. */
.inline-thread.htmx-request { opacity: .55; }
.inline-thread.htmx-request .inline-thread-actions { pointer-events: none; }
.inline-thread-status {
  align-self: flex-start; font-size: .68rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: var(--fg-muted);
  border: 1px solid var(--line); border-radius: var(--pill); padding: 1px 8px;
}

.inline-comment { display: flex; flex-direction: column; gap: 4px; }
.inline-comment + .inline-comment { border-top: 1px solid var(--line-soft); padding-top: 8px; }
.inline-comment-head { display: flex; align-items: center; gap: 8px; font-size: .8rem; }
.inline-comment-author { font-weight: 600; color: var(--fg); }
.inline-comment-when { color: var(--fg-dim); font-size: .74rem; }
.inline-comment-body { font-size: .88rem; line-height: 1.5; }
/* Permalink to this remark (issue #104): quiet until the row is hovered or the
   link itself is focused, so it never competes with the comment. */
.inline-comment-link {
  margin-left: auto; color: var(--fg-dim); font-family: var(--mono); font-size: .8rem;
  text-decoration: none; opacity: 0; transition: opacity .12s ease;
}
.inline-comment:hover .inline-comment-link, .inline-comment-link:focus-visible { opacity: 1; }
.inline-comment-link:hover { color: var(--accent-2); }
/* …and when one is what the URL points at, say so. */
.inline-comment:target {
  border-left: 2px solid var(--accent-2); padding-left: 8px;
  background: color-mix(in oklab, var(--accent-2) 8%, transparent);
}
.inline-comment:target .inline-comment-link { opacity: 1; }

/* Intent symbol: prominent, distinct glyph. Colour REINFORCES the symbol; it is
   never the only signal (the symbol + a11y label carry the meaning). */
.intent-symbol { font-size: 1rem; line-height: 1; }
.intent-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 6px; flex-shrink: 0;
  border: 1px solid var(--line); background: var(--bg-elev);
}
.inline-comment.intent-info .intent-badge { color: var(--accent-2); border-color: color-mix(in oklab, var(--accent-2) 45%, var(--line)); }
.inline-comment.intent-request_change .intent-badge { color: var(--danger); border-color: color-mix(in oklab, var(--danger) 45%, var(--line)); }
.inline-comment.intent-kudos .intent-badge { color: var(--accent); border-color: var(--accent-line); }

/* Intent picker: three symbol chips; the checked one lights up. */
.intent-picker { display: flex; gap: 8px; border: 0; padding: 0; margin: 0; flex-wrap: wrap; }
.intent-legend { font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--fg-muted); padding: 0; margin-bottom: 4px; width: 100%; }
.intent-option {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  border: 1px solid var(--line); border-radius: 7px; padding: 4px 10px;
  font-size: .8rem; color: var(--fg-muted); background: var(--bg-elev);
}
.intent-radio { position: absolute; width: 1px; height: 1px; opacity: 0; }
.intent-option:has(.intent-radio:checked) { color: var(--fg); border-color: var(--accent); background: var(--accent-soft); }
.intent-option:has(.intent-radio:focus-visible) { box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 30%, transparent); }
.intent-option.intent-request_change:has(.intent-radio:checked) { border-color: var(--danger); background: color-mix(in oklab, var(--danger) 10%, transparent); }
.intent-option.intent-info:has(.intent-radio:checked) { border-color: var(--accent-2); background: color-mix(in oklab, var(--accent-2) 12%, transparent); }

.inline-comment-form { display: flex; flex-direction: column; gap: 8px; max-width: 760px; }
.inline-textarea {
  width: 100%; resize: vertical; font: inherit; font-size: .88rem;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--fg);
}
.inline-textarea:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in oklab, var(--accent) 25%, transparent); }
.inline-form-actions { display: flex; gap: 8px; }
.inline-thread-actions { display: flex; align-items: center; gap: 12px; }
.inline-disclosure { cursor: pointer; font-size: .8rem; color: var(--accent-2); user-select: none; list-style: none; }
.inline-disclosure::-webkit-details-marker { display: none; }
.inline-add { margin-top: 2px; }

/* PR Checks tab + merge-box summaries (CI wired to the head commit).
   .pr-checks-rollup and .pr-checks-summary look alike but are NOT duplicates and
   neither is dead: -rollup is the Checks TAB's bare heading line (templ
   checksRollup), -summary is the merge BOX's bordered card next to the merge
   actions (templ prChecksSummary). Issue #108 read one of them as unused and
   proposed deleting it; deleting either unstyles a live page. The list page's own
   verdict line is a third thing again — .pr-item-status in pages/pr.css. */
.pr-checks-rollup { display: flex; align-items: center; gap: 10px; margin: 4px 0 14px; }
.pr-checks-subhead { margin: 16px 0 6px; font-size: .8rem; text-transform: uppercase;
  letter-spacing: .06em; color: var(--fg-muted); }
.pr-checks-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pr-check-row { display: flex; align-items: center; gap: 10px; padding: 6px 10px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--bg-elev); }
.pr-check-name { font-family: var(--mono); font-size: .84rem; color: var(--fg); text-decoration: none; }
.pr-check-name:hover { text-decoration: underline; }
.pr-checks-summary { display: flex; align-items: center; gap: 10px; margin: 14px 0 0;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-elev); }
.pr-checks-summary-failure { border-color: color-mix(in oklab, var(--danger) 45%, var(--line)); }
.pr-checks-summary-success { border-color: color-mix(in oklab, var(--success) 40%, var(--line)); }

/* On-load mergeability status in the merge box. */
.pr-mergeability { display: flex; align-items: center; gap: 10px; margin: 12px 0 0;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--bg-elev);
  font-size: .88rem; }
.pr-mergeability-mark { font-weight: 700; }
.pr-mergeability-clean { border-color: color-mix(in oklab, var(--success) 45%, var(--line)); }
.pr-mergeability-clean .pr-mergeability-mark { color: var(--success); }
.pr-mergeability-diverged { border-color: color-mix(in oklab, var(--accent-2) 45%, var(--line)); }
.pr-mergeability-diverged .pr-mergeability-mark { color: var(--accent-2); }
.pr-mergeability-empty .pr-mergeability-mark { color: var(--fg-dim); }
.pr-mergeability-conflict { border-color: color-mix(in oklab, var(--danger) 45%, var(--line)); }
.pr-mergeability-conflict .pr-mergeability-mark { color: var(--danger); }
}

/* ==== css/pages/dashboard.css ==== */
@layer pages {
/* ---- avatar chip ---- */
.avatar { width: 64px; height: 64px; border-radius: 18px; display: grid; place-items: center;
  font-size: 1.7rem; font-weight: 700; color: var(--accent-ink); flex-shrink: 0;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.14), 0 8px 22px -10px color-mix(in oklab, var(--accent) 55%, transparent); }
.avatar.small { width: 26px; height: 26px; border-radius: 8px; font-size: .82rem; box-shadow: inset 0 0 0 1px rgba(255,255,255,.14); }

/* ---- dashboard ---- */
.dash { padding: 44px 0 72px; }
.dash-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px;
  flex-wrap: wrap; margin-bottom: 12px; }
/* NOT mono — same story as .settings-title and .pr-detail-title, from a third
   commit. 90e4d9b ("design-system pass 2") rewrote this rule as
   `font-family: var(--mono); font-weight: 650; clamp(1.45rem, 3vw, 1.9rem);
   -.02em` and justified it in one clause: "Dashboard title also picks up the
   mono voice". The design system's dashboard.css carries that rule with every
   one of those numbers and no font-family, so it inherits the body sans — the
   sizing was a real design change and the family was never part of it.
   Three surfaces render this title and none of them is an identifier: "Welcome
   back, @rick" (the handle inside is a <span class="grad">, not code),
   "Public repositories" on /explore, "Search rickub". The identifiers BELOW
   them keep the mono voice — .explore-list .settings-item-title renders each
   result's owner/name as data, which is the distinction mono is for.
   Pinned by TestDashTitleWearsTheBodyFace. */
.dash-title { margin: 0; font-weight: 650; font-size: clamp(1.45rem, 3vw, 1.9rem); letter-spacing: -.02em; }
.dash-title .grad {
  background: linear-gradient(112deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.dash-sub { margin: 6px 0 0; color: var(--fg-muted); }
.dash-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.dash-subhead { margin-top: 0; }
.dash-grid { display: grid; grid-template-columns: 1fr; gap: 32px; align-items: start; }
.dash-main { min-width: 0; }
.dash-side { min-width: 0; }
/* .empty-nudge and friends moved to components/empty-nudge.css — five pages use
   them (dashboard, explore, search, releases, CI runs), so they are a component,
   not this page's furniture. */
}

/* ==== css/pages/explore.css ==== */
@layer pages {
/* ---- explore ---- */
.explore { padding: 44px 0 72px; max-width: 820px; }
.explore-head { margin-bottom: 18px; }
.explore-search { display: flex; gap: 10px; margin-bottom: 24px; }
.explore-search .field-input { flex: 1; }
/* repo identifiers read as data — mono, like a result row in the palette */
.explore-list .settings-item-title { font-family: var(--mono); font-size: .92rem; letter-spacing: -.01em; }
.explore-owner { color: var(--fg-muted); }
.explore-list .settings-item-title .sep { color: var(--fg-dim); margin-inline: 3px; }
.pager { display: flex; align-items: center; gap: 12px; margin-top: 20px; }
.pager-page { font: .84rem var(--mono); color: var(--fg-dim); }

/* Branch/tag list render-cap: "showing N of M" note + a show-all link. */
.ref-showall { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 16px; }
}

/* ==== css/pages/profile.css ==== */
@layer pages {
/* ---- profile ---- */
.profile { padding: 44px 0 72px; }
.profile-header { display: flex; align-items: flex-start; gap: 20px; margin-bottom: 8px; }
.profile-id { flex: 1; min-width: 0; }
.profile-handle { margin: 0; font-size: 1.6rem; letter-spacing: -.02em; }
.profile-name { margin: 2px 0 0; color: var(--fg-muted); }
.profile-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 10px; }
.profile-meta-item { font: .82rem var(--mono); color: var(--fg-dim); }
.profile-bio { margin: 12px 0 0; color: var(--fg-muted); max-width: 60ch; }
.profile-actions { display: flex; gap: 10px; flex-wrap: wrap; }
.profile-tabs { display: flex; gap: 6px; border-bottom: 1px solid var(--line); margin: 22px 0 24px; }
.profile-tab { padding: 8px 14px; font-size: .92rem; font-weight: 500; color: var(--fg-muted);
  border-bottom: 2px solid transparent; margin-bottom: -1px; }
.profile-tab:hover { color: var(--fg); }
.profile-tab.active { color: var(--fg); font-weight: 600; border-bottom-color: var(--accent); }
.profile-body { display: grid; grid-template-columns: 1fr; gap: 32px; align-items: start; }
.profile-main { min-width: 0; }
.profile-side { min-width: 0; }
.member-avatars { list-style: none; margin: 10px 0 12px; padding: 0; display: grid; gap: 6px; }
.member-chip { display: flex; align-items: center; gap: 8px; color: var(--fg); font-size: .9rem; }
.member-chip:hover { color: var(--accent); }
.profile-side-link { font-size: .88rem; color: var(--accent-2); font-weight: 600; }

@media (min-width: 54rem) {
  .dash-grid { grid-template-columns: 1fr 320px; }
  .profile-body { grid-template-columns: 1fr 260px; }
}
}

/* ==== css/pages/actions.css ==== */
@layer pages {
/* CI (Actions) */
.ci-status { display: inline-flex; align-items: center; gap: 9px; font: 500 .8rem var(--sans); text-transform: capitalize; }
.ci-status::before { content: ""; flex: none; width: 8px; height: 8px; border-radius: 50%; background: currentColor; box-shadow: 0 0 0 3px color-mix(in oklab, currentColor 16%, transparent); }
.ci-status-running::before { animation: ci-run-pulse 1.6s ease-in-out infinite; }
@keyframes ci-run-pulse { 50% { box-shadow: 0 0 0 6px color-mix(in oklab, currentColor 8%, transparent); } }
@media (prefers-reduced-motion: reduce) { .ci-status-running::before { animation: none; } }
.ci-status-queued { color: var(--fg-dim); }
.ci-status-running { color: var(--warn); }
.ci-status-success { color: var(--success); }
.ci-status-failure, .ci-status-cancelled { color: var(--danger); }
.ci-status-skipped { color: var(--fg-dim); }
.ci-status-pending_approval { color: var(--warn); }
/* "pending" is the ROLL-UP's word for "something is still in flight" — what
   rollupChecks returns, and what rollupClass substitutes for an empty state. It
   had no entry here, so the one pill on the site that summarizes a whole merge
   request's CI was also the one pill with no status colour: its dot inherited
   the surrounding text colour. Same amber as `running`, because that is what it
   means. (Part of #117.) */
.ci-status-pending { color: var(--warn); }
.ci-run-status { display: flex; align-items: center; gap: 12px; margin: 0 0 16px; }
.ci-rerun { margin-left: auto; }
/* Terminal-only run controls (the finished-run summary + the Re-run form) are
   server-rendered hidden and revealed by the "ci-run-live" island
   (web/frontend/src/islands/ci-run-live.ts) when a run finishes live.
   The bare UA [hidden] rule is beaten by any author `display` on the element —
   the Re-run form carries the generic `.inline-form { display: inline }` — so we
   re-assert display:none via an author selector of matching specificity; the
   reveal removes [hidden], the selector stops matching, and the element falls
   back to its normal display. */
[data-ci-terminalonly][hidden] { display: none; }
.ci-runs-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
/* The section heading sits inline in these flex heads (not as a full-width
   rule), so its shared top border would render as a stray orphan line above the
   text — drop the border/top-spacing here. */
.ci-runs-head .settings-subhead, .ci-run-head .settings-subhead { border-top: none; padding-top: 0; margin-top: 0; }
/* CI-minute meter line. inline-flex so the <progress> and the "Manage plan"
   link sit ON the sentence rather than under it, and wrap together on narrow
   screens instead of the bar dangling alone. */
.ci-usage { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); font-size: .85rem; color: var(--fg-muted); margin: 0 0 16px; }
.ci-usage-over { color: var(--danger); }
.ci-usage-note { font-weight: 500; }
.ci-usage-manage { font-weight: 600; }
/* The meter reuses .milestone-bar (issues.css) rather than restyling <progress>
   a second time; only its length differs here. */
.ci-usage .milestone-bar { width: 140px; }

/* ---- run-status filter (All / Success / Failure / Running) ----
   A segmented control of LINKS carrying ?status=, so the filtered view is
   linkable and the server does the filtering. Geometry lives here; the colours
   are the same accent-soft selection used by the ref switcher's active item. */
.ci-runs-filterbar { display: flex; align-items: center; gap: 10px; margin: 0 0 12px; }
.ci-filter {
  display: inline-flex; overflow: hidden;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg-elev);
}
.seg-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; font-size: .85rem; font-weight: 600;
  color: var(--fg-muted); text-decoration: none;
  border-right: 1px solid var(--line-soft);
}
.seg-btn:last-child { border-right: 0; }
.seg-btn:hover { color: var(--fg); background: var(--bg-elev-2); }
.seg-btn.active { background: var(--accent-soft); color: var(--fg); }
.seg-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
.ci-secrets-note { font-size: .82rem; color: var(--fg-muted); margin: 0 0 12px; }
/* Visible degradation path (docs/architecture/web-frontend-islands.md §2 Gap 3): the
   ci-run-live island reveals this (removes [hidden]) after repeated SSE reconnect
   failures, instead of the page silently going stale with no signal to the user. */
.ci-sse-banner { font-size: .85rem; font-weight: 500; color: var(--warn); background: color-mix(in oklab, var(--warn) 10%, transparent);
  border: 1px solid color-mix(in oklab, var(--warn) 35%, transparent); border-radius: var(--radius); padding: 8px 12px; margin: 0 0 12px; }
.ci-sse-banner[hidden] { display: none; }
.ci-dispatch { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ci-dispatch-label { font-size: .82rem; color: var(--fg-muted); }
.ci-dispatch-wf { min-width: 12rem; }
/* 12rem is the design system's width, and it is also the .ci-dispatch-wf min-width
   above — the two fields read as one pair rather than as a wide select and a short
   box. 10rem fits the "branch (default)" placeholder and little else; a real branch
   name is what gets typed here. Both the form and .ci-runs-head above it wrap. */
.ci-dispatch-ref { width: 12rem; }
/* One-shot outcome banner after a manual dispatch, and the hint shown in place of a
   dead button when the repo has no workflow_dispatch workflow. */
.ci-dispatch-notice { margin: 0 0 16px; padding: 8px 12px; border: 1px solid var(--line);
  border-left: 3px solid var(--accent); border-radius: var(--radius); background: var(--bg-elev);
  font-size: .85rem; color: var(--fg); }
.ci-dispatch-hint { margin: 0 0 16px; font-size: .82rem; color: var(--fg-muted); }
/* Actions runs page: a GitHub-style two-column master layout — the "Workflows" rail
   (defined workflows, visible before any run) beside the runs column. Collapses to a
   single column on narrow screens (rail first, so a new repo still sees its CI). */
.ci-actions-layout { display: grid; grid-template-columns: 1fr; gap: 24px; align-items: start; margin-bottom: 40px; }
@media (min-width: 900px) { .ci-actions-layout { grid-template-columns: minmax(220px, 300px) 1fr; } }
.ci-actions-main { min-width: 0; }
.ci-wf-rail { min-width: 0; }
.ci-wf-rail-head { margin-top: 0; }
.ci-wf-empty { margin: 0; font-size: .82rem; line-height: 1.55; color: var(--fg-muted); }
.ci-wf-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.ci-wf-item { display: grid; gap: 6px; padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); }
.ci-wf-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.ci-wf-name { font-weight: 600; color: var(--accent); text-decoration: none; overflow-wrap: anywhere; }
.ci-wf-name:hover { text-decoration: underline; }
.ci-wf-norun { flex: none; font: .72rem var(--sans); color: var(--fg-muted); border: 1px dashed var(--line); border-radius: var(--pill); padding: 2px 9px; white-space: nowrap; }
.ci-wf-triggers { display: flex; flex-wrap: wrap; gap: 4px; }
.ci-wf-trigger { font: 600 .7rem var(--sans); text-transform: uppercase; letter-spacing: .04em; color: var(--fg-dim); border: 1px solid var(--line); border-radius: var(--pill); padding: 1px 7px; }
.ci-wf-path { font: .72rem var(--mono); color: var(--fg-dim); overflow-wrap: anywhere; }
.ci-run-list { list-style: none; margin: 4px 0 40px; padding: 0; border-top: 1px solid var(--line-soft); }
.ci-run-item { display: flex; align-items: center; gap: 16px; padding: 13px 6px; border-bottom: 1px solid var(--line-soft); transition: background .12s ease; }
.ci-run-item:hover { background: color-mix(in oklab, var(--fg) 4%, transparent); }
.ci-run-item .ci-status { order: -1; flex: none; min-width: 6.5rem; }
.ci-run-link { display: grid; gap: 2px; flex: 1; text-decoration: none; color: inherit; }
.ci-run-link:hover .ci-run-workflow { color: var(--accent); }
.ci-run-workflow { font-weight: 500; font-size: .97rem; letter-spacing: -.005em; }
.ci-run-meta { font: .8rem var(--mono); color: var(--fg-dim); }
.ci-run-when { font: .8rem var(--mono); color: var(--fg-dim); white-space: nowrap; }
.ci-run-head { display: flex; align-items: center; gap: 12px; margin: 8px 0 20px; flex-wrap: wrap; }
.ci-job { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); margin-bottom: 10px; }
.ci-job-summary { display: flex; align-items: center; gap: 10px; padding: 12px 16px; cursor: pointer; }
.ci-job-name { font-weight: 600; }
.ci-steps { list-style: none; margin: 0; padding: 8px 16px; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 6px; }
.ci-step { display: flex; align-items: center; gap: 10px; }
.ci-step-name { font: .85rem/1.4 var(--mono); color: var(--fg); word-break: break-word; }
/* Elapsed/duration readout, pushed to the row's trailing edge. It is empty for a
   queued step and for the first second of a running one, and `margin-left: auto`
   costs nothing when the element has no text, so the row never reserves a gap for
   it. `tabular-nums` keeps the digits from jittering as the timer ticks. The
   running step's value is written by lib/step-elapsed.ts — no inline style is
   involved anywhere (style-src is 'self' with no 'unsafe-inline'). */
.ci-step-elapsed { margin-left: auto; padding-left: 12px; font: .78rem/1.4 var(--mono); font-variant-numeric: tabular-nums; color: var(--fg-dim); white-space: nowrap; }
.ci-step-elapsed[data-running="true"] { color: var(--warn); }
/* Step-split log viewer: the job body is a stack of collapsible <details> sections
   (one per "== step ==" header), each holding its own terminal-styled log. */
.ci-job-log { border-top: 1px solid var(--line); background: #0d1117; border-radius: 0 0 var(--radius) var(--radius); overflow: hidden; }
.ci-log-section { border-bottom: 1px solid #21262d; }
.ci-log-section:last-child { border-bottom: 0; }
.ci-log-section-summary, .ci-log-group-summary { display: flex; align-items: center; gap: 8px; padding: 8px 16px; cursor: pointer; list-style: none; color: #e6edf3; font: .82rem/1.4 var(--mono); user-select: none; }
.ci-log-section-summary::-webkit-details-marker, .ci-log-group-summary::-webkit-details-marker { display: none; }
.ci-log-section-summary:hover, .ci-log-group-summary:hover { background: #161b22; }
.ci-log-caret { flex: none; width: 0; height: 0; border-left: 5px solid #7d8590; border-top: 4px solid transparent; border-bottom: 4px solid transparent; transition: transform .12s ease; }
details[open] > .ci-log-section-summary > .ci-log-caret, details[open] > .ci-log-group-summary > .ci-log-caret { transform: rotate(90deg); }
.ci-log-section-name { font-weight: 600; word-break: break-word; }
.ci-log-section-flag { flex: none; margin-left: auto; width: 16px; height: 16px; border-radius: 50%; background: #da3633; color: #fff; font-weight: 700; font-size: .7rem; line-height: 16px; text-align: center; }
.ci-log-section[data-has-error] > .ci-log-section-summary .ci-log-section-name { color: #ff7b72; }

/* THE STEP'S OWN FURNITURE, INSIDE AN ALWAYS-DARK PANE.
   The step list that used to carry the status badge and the elapsed readout sat
   on the page's normal, theme-following ground. They live in the log section
   summary now, and .ci-job-log hard-codes background: #0d1117 in BOTH themes — so
   every color they inherit has to be a dark-pane color, not a token. This is the
   same failure .ci-logline-warning documents a few rules below: --warn is #d29922
   in dark but #9a6700 in LIGHT, and a light theme would paint it on #0d1117.
   --fg-dim, --success and --danger fail the same way, and worse for queued and
   skipped, whose whole badge is one dim word.
   The values are the dark palette this file already uses for log lines. */
.ci-log-section-summary > .ci-status { flex: none; font-family: var(--mono); font-size: .78rem; }
.ci-log-section-summary > .ci-status-queued,
.ci-log-section-summary > .ci-status-skipped { color: #8b949e; }
.ci-log-section-summary > .ci-status-running,
.ci-log-section-summary > .ci-status-pending,
.ci-log-section-summary > .ci-status-pending_approval { color: #d29922; }
.ci-log-section-summary > .ci-status-success { color: #3fb950; }
.ci-log-section-summary > .ci-status-failure,
.ci-log-section-summary > .ci-status-cancelled { color: #ff7b72; }
.ci-log-section-summary > .ci-step-elapsed { color: #8b949e; }
.ci-log-section-summary > .ci-step-elapsed[data-running="true"] { color: #d29922; }
/* ONE right-aligned item. The flag claims margin-left:auto for the case where it
   is last; with an elapsed readout after it, two auto margins split the free space
   and park the flag in the middle of the row. The elapsed is the right edge, so
   the flag goes back to sitting beside the name it qualifies. */
.ci-log-section-summary > .ci-log-section-flag { margin-left: 0; }
/* A step the log has not reached yet: no output to open, so the row reads as a
   plan rather than a control. The caret is hidden (there is nothing to fold) and
   the whole row is dimmed against the sections that have actually run. */
.ci-log-section[data-log-pending] { opacity: .62; }
.ci-log-section[data-log-pending] > .ci-log-section-summary { cursor: default; }
.ci-log-section[data-log-pending] > .ci-log-section-summary > .ci-log-caret { visibility: hidden; }
.ci-log-group { border-top: 1px solid #21262d; }
.ci-log-group-summary { padding-left: 28px; color: #adbac7; }
/* .ci-log is the terminal pane: a run of block-level lines (no significant markup
   whitespace, so it works whether server- or client-rendered). */
.ci-log { margin: 0; padding: 10px 16px; background: #0d1117; color: #e6edf3; font: .82rem/1.5 var(--mono); overflow-x: auto; }
.ci-log-group > .ci-log { padding-left: 28px; }
.ci-logline { display: block; white-space: pre-wrap; word-break: break-word; min-height: 1.24em; }
.ci-logline-error { color: #ff7b72; background: rgba(248, 81, 73, .12); }
/* LITERAL, not var(--warn), and that is the opposite of the usual mistake.
   .ci-log hard-codes `background: #0d1117` — the log pane is always dark, in
   both themes, by design. --warn is #d29922 in dark but #9a6700 in LIGHT, so a
   themed warning line rendered dark ochre on near-black at roughly 3.7:1, under
   the 4.5:1 AA floor, while .ci-logline-error and -debug beside it stayed
   correct because they were already literal. A colour whose background cannot
   change must not be a token. */
.ci-logline-warning { color: #d29922; }
.ci-logline-debug { color: #6e7681; }
.ci-log-empty { color: var(--fg-dim); padding: 12px 16px; }
/* Follow ("tail -f") toggle. */
.ci-follow.is-active { background: var(--accent, #2f81f7); color: #fff; border-color: transparent; }
.ci-follow.is-active::before { content: "\25BC\FE0E"; margin-right: 5px; font-size: .7em; }
.ci-artifacts { margin-top: 24px; }
.ci-artifact-list { list-style: none; margin: 8px 0 0; padding: 0; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); }
.ci-artifact { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; }
.ci-artifact + .ci-artifact { border-top: 1px solid var(--line); }
.ci-artifact-name { font-weight: 600; }
.ci-artifact-size { white-space: nowrap; }

/* Run dependency graph — topological stages as columns (parallel jobs side by side,
   dependents in later columns) with an SVG edge overlay. It scrolls horizontally on
   overflow so the page body never does. */
.ci-graph { overflow-x: auto; margin: 4px 0 24px; padding-bottom: 4px; }
.ci-graph-stages { position: relative; display: flex; gap: 44px; align-items: center; width: max-content; min-width: 100%; }
.ci-graph-edges { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; overflow: visible; }
.ci-graph-edge { fill: none; stroke: var(--line); stroke-width: 2; }
.ci-graph-stage { position: relative; z-index: 1; display: flex; flex-direction: column; gap: 16px; justify-content: center; min-width: 190px; }
.ci-node { border: 1px solid var(--line); border-left: 3px solid var(--fg-dim); border-radius: var(--radius); background: var(--bg-elev); padding: 10px 12px; display: grid; gap: 8px; }
.ci-node-name { font-weight: 600; word-break: break-word; }
.ci-node-name-link { display: block; text-decoration: none; color: inherit; border-radius: var(--radius); }
.ci-node-name-link:hover { color: var(--accent); }
.ci-node-success { border-left-color: var(--success); }
.ci-node-failure, .ci-node-cancelled { border-left-color: var(--danger); }
.ci-node-skipped { border-left-color: var(--fg-dim); }
.ci-node-queued { border-left-color: var(--line); }
.ci-node-running { border-left-color: var(--warn); animation: ci-node-pulse 1.6s ease-in-out infinite; }
@keyframes ci-node-pulse { 50% { border-left-color: color-mix(in oklab, var(--warn) 35%, transparent); } }
.ci-node-legs { display: flex; flex-direction: column; gap: 6px; }
.ci-node-leg { display: flex; align-items: center; gap: 8px; text-decoration: none; color: inherit; border-radius: var(--radius); padding: 2px 4px; }
.ci-node-leg:hover { background: color-mix(in oklab, var(--accent) 12%, transparent); }
.ci-node-leg.is-focused { background: color-mix(in oklab, var(--accent) 18%, transparent); outline: 1px solid var(--accent); }
.ci-node-leg-label { font: .8rem var(--mono); color: var(--fg); word-break: break-word; }
.ci-node-leg-dur { font: .7rem var(--mono); color: var(--fg-dim); margin-left: auto; padding-left: 8px; white-space: nowrap; }

/* GitHub-style split: job sidebar (left) + the focused job's logs (main panel).
   Mobile-first: single column with a static sidebar; the two-column sticky split
   is enhanced in at the min-width breakpoint. */
.ci-run-body { display: grid; grid-template-columns: 1fr; gap: 20px; align-items: start; margin-bottom: 40px; }
.ci-job-sidebar { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); overflow: hidden; }
.ci-sidebar-head { margin: 0; padding: 10px 14px; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; color: var(--fg-muted); border-bottom: 1px solid var(--line); }
.ci-sidebar-list { list-style: none; margin: 0; padding: 0; }
.ci-sidebar-list li + li .ci-sidebar-row { border-top: 1px solid var(--line); }
.ci-sidebar-row { display: flex; align-items: center; gap: 8px; padding: 8px 12px; text-decoration: none; color: inherit; border-left: 3px solid transparent; }
.ci-sidebar-row:hover { background: color-mix(in oklab, var(--accent) 8%, transparent); }
.ci-sidebar-row.is-selected { background: color-mix(in oklab, var(--accent) 14%, transparent); border-left-color: var(--accent); }
.ci-sidebar-name { font: .82rem/1.35 var(--mono); word-break: break-word; }
.ci-sidebar-row:has(.ci-status-running) .ci-sidebar-name { color: var(--warn); font-weight: 600; }
.ci-run-main { min-width: 0; }
.ci-focused-job { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); }
.ci-focused-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; }
@media (min-width: 45rem) {
  .ci-run-body { grid-template-columns: minmax(200px, 260px) 1fr; }
  .ci-job-sidebar { position: sticky; top: 16px; }
}

/* ---- run detail redesign: master-detail dashboard --------------------------
   A summary card (status + timing + stat tiles) over pure-CSS tabs (Jobs /
   Timeline / Logs / Artifacts), with a failure-first digest hoisted above the
   tabs on a red run. Reuses the status palette from the .ci-status badges. */

/* status fills, shared by mini-bar segments, timeline bars, and label dots */
.ci-seg-success { background: var(--success); }
.ci-seg-failure, .ci-seg-cancelled { background: var(--danger); }
.ci-seg-running { background: var(--warn); }
.ci-seg-queued, .ci-seg-pending_approval { background: var(--bg-elev-2); }
.ci-seg-skipped { background: var(--fg-dim); }

/* summary card */
.ci-summary { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); padding: 16px 18px; margin: 8px 0 18px; }
.ci-summary-bad { border-left: 3px solid var(--danger); }
/* DELIBERATE DIVERGENCE from the design system, which says `align-items: center`
   (docs/design-sync.md, 2026-08-29). Its card is a status + title + one meta line;
   ours is up to three rows tall — .ci-summary-id adds .ci-run-meta and the
   terminal-only .ci-run-summary, and .ci-summary-actions carries Re-run / Approve /
   Cancel. Centring a 19px status dot against a 3-line block lands it beside the
   "push · main · a1b2c3d" line, so the dot reads as labelling the branch rather
   than the run, and it drops the write controls to the card's vertical middle.
   flex-start keeps both on the title's line. See TestSummaryTopTopAlignsToTheTitle. */
.ci-summary-top { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.ci-summary-id { display: grid; gap: 3px; min-width: 0; }
.ci-summary-title { margin: 0; font-size: 1.1rem; font-weight: 600; }
.ci-summary-title .ci-run-meta { font-weight: 500; }
.ci-summary-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ci-run-summary:empty { display: none; }

/* duration mini-bar: thin stacked track across the run's wall-clock span */
.ci-minibar { position: relative; height: 8px; margin: 14px 0 4px; border: 1px solid var(--line); border-radius: 5px; background: var(--bg); overflow: hidden; }
.ci-minibar-seg { position: absolute; top: 0; bottom: 0; min-width: 2px; opacity: .9; }

/* timing breakdown under the mini-bar */
.ci-timing { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 8px; font: .74rem var(--mono); color: var(--fg-muted); }
.ci-timing b { color: var(--fg); font-weight: 600; }

/* stat tiles */
.ci-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 14px; }
.ci-tile { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg); padding: 10px 12px; }
.ci-tile-v { display: block; font: 700 1.3rem/1 var(--mono); }
.ci-tile-k { display: block; font-size: .72rem; color: var(--fg-muted); margin-top: 6px; }
.ci-tile-bad .ci-tile-v { color: var(--danger); }

/* failure-first digest */
.ci-fail-digest { border: 1px solid var(--danger-line, color-mix(in oklab, var(--danger) 45%, var(--line))); border-left: 4px solid var(--danger); border-radius: var(--radius); background: color-mix(in oklab, var(--danger) 7%, var(--bg-elev)); padding: 14px 16px; margin: 0 0 18px; }
.ci-fail-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ci-fail-job { font: 600 .95rem var(--mono); }
.ci-flex-spacer { flex: 1; }
.ci-fail-lines { margin-top: 12px; background: #0d1117; border-radius: var(--radius); padding: 10px 14px; overflow-x: auto; display: grid; }
.ci-fail-lines .ci-logline { font: .8rem/1.5 var(--mono); white-space: pre-wrap; word-break: break-word; }

/* pure-CSS tabs (hidden radios drive panel visibility + active label) */
.ci-tabwrap { margin-bottom: 40px; }
.ci-tab-radio { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.ci-tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line); flex-wrap: wrap; }
/* A tab is a <label>, and half its content is the .ci-tab-count pill beside "Jobs"
   and "Artifacts", so the box half of this rule is load-bearing, not decoration.
   .ci-tabs is a flex container, so .ci-tab's display is BLOCKIFIED either way —
   the choice is block vs flex, not inline vs block. As a block the label laid its
   word and its pill out as inline content, so the pill sat on the text BASELINE
   (its own line-height and 1px/7px padding pulling it off-centre) and `gap` did
   not apply, leaving the spacing to the literal space in the markup; a block also
   wraps, so "Artifacts 3" could break inside the tab rather than the bar breaking
   between tabs. The design system's rule; we had only the paint half of it. */
.ci-tab { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; font: 600 .85rem var(--sans); color: var(--fg-muted); padding: 9px 14px; border-bottom: 2px solid transparent; cursor: pointer; user-select: none; }
.ci-tab:hover { color: var(--fg); }
.ci-tab-count { font: .7rem var(--mono); background: var(--bg-elev-2); border-radius: var(--pill); padding: 1px 7px; margin-left: 5px; color: var(--fg-muted); }
.ci-tab-radio:focus-visible + .ci-tabs .ci-tab, .ci-tab:focus-within { outline: 2px solid var(--accent); outline-offset: -2px; }
.ci-panel { display: none; padding-top: 18px; }
/* activate the label + panel whose radio is checked */
#ci-tab-jobs:checked ~ .ci-tabs [for="ci-tab-jobs"],
#ci-tab-timeline:checked ~ .ci-tabs [for="ci-tab-timeline"],
#ci-tab-logs:checked ~ .ci-tabs [for="ci-tab-logs"],
#ci-tab-artifacts:checked ~ .ci-tabs [for="ci-tab-artifacts"] { color: var(--fg); border-bottom-color: var(--accent); }
#ci-tab-jobs:checked ~ .ci-panels .ci-panel-jobs,
#ci-tab-timeline:checked ~ .ci-panels .ci-panel-timeline,
#ci-tab-logs:checked ~ .ci-panels .ci-panel-logs,
#ci-tab-artifacts:checked ~ .ci-panels .ci-panel-artifacts { display: block; }

/* jobs tab = stage pipeline board (reuses .ci-graph); a per-column stage header
   labels each topological stage, so the columns read as pipeline phases */
.ci-graph-stage-head { font: 600 .68rem var(--sans); text-transform: uppercase; letter-spacing: .05em; color: var(--fg-muted); padding-bottom: 4px; margin-bottom: 4px; border-bottom: 1px solid var(--line-soft); }

/* timeline */
.ci-timeline { display: grid; gap: 2px; }
.ci-tl-row { display: grid; grid-template-columns: 160px 1fr; gap: 12px; align-items: center; }
.ci-tl-label { font: .82rem var(--mono); color: var(--fg); display: flex; align-items: center; gap: 7px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ci-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.ci-tl-track { position: relative; height: 26px; border-bottom: 1px solid var(--line-soft); }
.ci-tl-bar { position: absolute; top: 6px; height: 14px; border-radius: 4px; display: flex; align-items: center; padding: 0 6px; overflow: hidden; text-decoration: none; min-width: 2px; }
.ci-tl-bar:hover { outline: 2px solid var(--accent); }
.ci-tl-dur { font: .64rem var(--mono); color: #fff; white-space: nowrap; }
.ci-tl-queued { position: absolute; left: 0; top: 6px; font: .68rem var(--mono); color: var(--fg-dim); }

/* logs tab toolbar */
.ci-logs-toolbar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; }

/* CSP-safe bar geometry (style-src 'self' blocks inline styles): offset + width
   snapped to 5% buckets, see components bucketPct — mirrors usage-fill-N. */
.ci-left-0{left:0} .ci-left-5{left:5%} .ci-left-10{left:10%} .ci-left-15{left:15%} .ci-left-20{left:20%} .ci-left-25{left:25%} .ci-left-30{left:30%} .ci-left-35{left:35%} .ci-left-40{left:40%} .ci-left-45{left:45%} .ci-left-50{left:50%} .ci-left-55{left:55%} .ci-left-60{left:60%} .ci-left-65{left:65%} .ci-left-70{left:70%} .ci-left-75{left:75%} .ci-left-80{left:80%} .ci-left-85{left:85%} .ci-left-90{left:90%} .ci-left-95{left:95%} .ci-left-100{left:100%}
.ci-w-0{width:0} .ci-w-5{width:5%} .ci-w-10{width:10%} .ci-w-15{width:15%} .ci-w-20{width:20%} .ci-w-25{width:25%} .ci-w-30{width:30%} .ci-w-35{width:35%} .ci-w-40{width:40%} .ci-w-45{width:45%} .ci-w-50{width:50%} .ci-w-55{width:55%} .ci-w-60{width:60%} .ci-w-65{width:65%} .ci-w-70{width:70%} .ci-w-75{width:75%} .ci-w-80{width:80%} .ci-w-85{width:85%} .ci-w-90{width:90%} .ci-w-95{width:95%} .ci-w-100{width:100%}

@media (max-width: 34rem) {
  .ci-tiles { grid-template-columns: repeat(2, 1fr); }
  .ci-tl-row { grid-template-columns: 100px 1fr; }
}
}

/* ==== css/pages/errors.css ==== */
@layer pages {
/* #10 — styled error page (404 / 500) inside the app shell. */
.error-page { display: grid; place-items: center; padding: var(--sp-section) 20px; min-height: 52vh; }
.error-card {
  max-width: 30rem; text-align: center;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: color-mix(in oklab, var(--bg-elev) 82%, transparent);
  padding: 40px 32px; box-shadow: var(--shadow-lg);
}
.error-code {
  font: 800 3.2rem/1 var(--mono); margin: 0 0 8px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.error-title { font-size: var(--text-xl); letter-spacing: -.02em; margin: 0 0 10px; }
.error-message { color: var(--fg-muted); margin: 0 0 22px; }
/* Two recovery routes, side by side and wrapping on a narrow viewport: "Back
   home" is a dead end for the case that produces most 404s (a stale link to a
   repo or an issue), so the card always offers a second way out. */
.error-actions { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
}

/* ==== css/pages/billing.css ==== */
@layer pages {
/* Billing page current-plan card + status pill. */
.billing-body { max-width: 820px; }
.billing-current { border: 1px solid var(--line); border-radius: var(--radius); padding: 18px 20px; margin: 8px 0 22px; background: var(--bg-elev); }
.billing-current-plan { font-size: 1.3rem; font-weight: 700; margin: 4px 0 6px; display: flex; align-items: center; gap: 10px; }
.billing-note { color: var(--fg-muted); font-size: var(--text-sm); margin: 2px 0; }
.billing-status {
  font: 600 var(--text-xs)/1 var(--sans); text-transform: uppercase; letter-spacing: .04em;
  padding: 4px 9px; border-radius: var(--pill); border: 1px solid var(--line); color: var(--fg-muted); background: var(--bg-elev-2);
}
.billing-status-active { color: var(--success); border-color: var(--success-line); background: var(--success-soft); }
.billing-status-pending { color: var(--warn); border-color: color-mix(in oklab, var(--warn) 40%, var(--line)); }
.billing-options { margin-top: 8px; }
.billing-cancel { margin-top: 18px; }

/* Usage page: metered rows with proportional bars, over-limit in danger tint. */
.usage-body { max-width: 720px; }
.usage-list { list-style: none; margin: 18px 0 24px; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.usage-row { border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; background: var(--bg-elev); }
.usage-row.usage-over { border-color: color-mix(in oklab, var(--danger) 55%, var(--line)); background: color-mix(in oklab, var(--danger) 8%, var(--bg-elev)); }
.usage-row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.usage-label { font-weight: 650; }
.usage-figure { font: var(--text-sm) var(--mono); color: var(--fg); }
.usage-limit { color: var(--fg-dim); }
.usage-meter { margin: 10px 0 4px; height: 8px; border-radius: var(--pill); background: var(--bg-elev-2); overflow: hidden; }
.usage-meter-fill { display: block; width: 0; height: 100%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); border-radius: var(--pill); transition: width .2s ease; }
.usage-meter-fill.usage-meter-over { background: var(--danger); }
/* CSP-safe fill widths: the strict CSP (style-src 'self') blocks inline
   style="width:…", so the fill fraction is carried by a class snapped to 5%
   buckets (see components.usageFillClass) instead. */
.usage-fill-0 { width: 0; }
.usage-fill-5 { width: 5%; }
.usage-fill-10 { width: 10%; }
.usage-fill-15 { width: 15%; }
.usage-fill-20 { width: 20%; }
.usage-fill-25 { width: 25%; }
.usage-fill-30 { width: 30%; }
.usage-fill-35 { width: 35%; }
.usage-fill-40 { width: 40%; }
.usage-fill-45 { width: 45%; }
.usage-fill-50 { width: 50%; }
.usage-fill-55 { width: 55%; }
.usage-fill-60 { width: 60%; }
.usage-fill-65 { width: 65%; }
.usage-fill-70 { width: 70%; }
.usage-fill-75 { width: 75%; }
.usage-fill-80 { width: 80%; }
.usage-fill-85 { width: 85%; }
.usage-fill-90 { width: 90%; }
.usage-fill-95 { width: 95%; }
.usage-fill-100 { width: 100%; }
.usage-detail { color: var(--fg-dim); font-size: var(--text-sm); }
.usage-over-note { color: var(--danger); }
.usage-warn-note { color: var(--warn); }

/* --- Compliant billing: profile form, invoices list, invoice document --- */
/* (`.billing-body select.field-input { appearance: none }` used to live here —
   a page-layer copy of what components/forms.css already says for every
   select.field-input in the app. Deleted, not moved.) */

/* Invoices list + invoice line-item table */
.invoice-table { width: 100%; border-collapse: collapse; margin-top: 8px; }
.invoice-table th {
  text-align: left; font-size: .78rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--fg-dim); font-weight: 600; padding: 8px 10px; border-bottom: 1px solid var(--line);
}
.invoice-table td { padding: 10px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
.invoice-amount { text-align: right; white-space: nowrap; }
.invoice-number { font-family: var(--mono); font-size: .9rem; }

/* Invoice detail: actions bar + the printable document */
.invoice-actions {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 16px;
}
.invoice-pdf-note { font-size: .84rem; color: var(--fg-muted); }
.invoice-doc {
  background: var(--bg-elev); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 28px;
}
.invoice-head {
  display: flex; justify-content: space-between; gap: 24px; flex-wrap: wrap;
  padding-bottom: 20px; border-bottom: 1px solid var(--line);
}
.invoice-title { margin: 0 0 6px; font-size: 1.6rem; }
.invoice-meta { margin: 2px 0; color: var(--fg-muted); }
.invoice-seller { text-align: right; font-size: .88rem; color: var(--fg-muted); }
.invoice-seller p { margin: 2px 0; }
.invoice-party-name { font-weight: 700; color: var(--fg); }
.invoice-buyer { padding: 18px 0; border-bottom: 1px solid var(--line-soft); font-size: .92rem; }
.invoice-buyer p { margin: 2px 0; color: var(--fg-muted); }
.invoice-lines { margin-top: 4px; }
.invoice-totals { margin-top: 18px; margin-left: auto; max-width: 320px; }
.invoice-total-row {
  display: flex; justify-content: space-between; gap: 24px;
  padding: 6px 0; color: var(--fg-muted);
}
.invoice-total-row span:last-child { font-variant-numeric: tabular-nums; }
.invoice-total-grand {
  border-top: 1px solid var(--line); margin-top: 4px; padding-top: 12px;
  font-weight: 700; font-size: 1.05rem; color: var(--fg);
}
.invoice-vat-note {
  margin-top: 20px; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--accent-soft); color: var(--fg); font-size: .88rem;
}
}

/* ==== css/pages/docs.css ==== */
@layer pages {
/* ============================ documentation (/docs) ============================ */
/* Two-column docs shell: a sticky sidebar table of contents + a readable content
   column. Reuses the site tokens (portal-green accent, mono labels, elevations). */
.docs-shell {
  display: grid;
  grid-template-columns: 248px minmax(0, 1fr);
  gap: 48px;
  align-items: start;
  padding-block: clamp(28px, 5vw, 56px);
}
.docs-sidebar {
  position: sticky; top: 84px;
  align-self: start;
  max-height: calc(100vh - 108px);
  overflow-y: auto;
  padding-right: 8px;
  font-size: .9rem;
}
.docs-home-link {
  display: block;
  font-weight: 700; letter-spacing: -.01em;
  padding: 6px 10px; margin-bottom: 14px;
  border-radius: var(--radius-sm);
}
.docs-home-link:hover { background: var(--bg-elev); }
/* Active state matches the list-item active below (same gutter-accent signature)
   so the highlighted tab looks identical whether the active item is the home
   link (e.g. /security) or a sub-item (e.g. /legal/terms). */
.docs-home-link.active {
  background: var(--bg-elev); color: var(--fg);
  box-shadow: inset 3px 0 0 var(--accent);
}
.docs-nav-group { margin-bottom: 18px; }
.docs-nav-title {
  display: block;
  font-family: var(--mono);
  font-size: .7rem; text-transform: uppercase; letter-spacing: .13em;
  color: var(--fg-dim);
  margin: 0 0 6px 10px;
}
.docs-sidebar ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px; }
.docs-sidebar li a {
  display: block;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  color: var(--fg-muted);
}
.docs-sidebar li a:hover { background: var(--bg-elev); color: var(--fg); }
.docs-sidebar li a.active {
  background: var(--bg-elev); color: var(--fg); font-weight: 600;
  box-shadow: inset 3px 0 0 var(--accent);
}

/* content column */
.docs-content { min-width: 0; max-width: 780px; }
.docs-content h1 { font-size: var(--text-3xl); letter-spacing: -.03em; line-height: 1.05; margin: 4px 0 14px; }
.docs-content h2 {
  font-size: var(--text-xl); letter-spacing: -.02em;
  margin: 40px 0 12px; padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.docs-content h3 { font-size: 1.06rem; margin: 26px 0 8px; }
.docs-content p { color: var(--fg-muted); margin: 0 0 14px; }
.docs-content .docs-lead { font-size: var(--text-lg); color: var(--fg); margin-bottom: 8px; }
.docs-content ul { color: var(--fg-muted); margin: 0 0 16px; padding-left: 22px; }
.docs-content li { margin: 4px 0; }
.docs-content a { color: var(--accent-2); }
.docs-content a:hover { text-decoration: underline; }
.docs-content strong { color: var(--fg); }
.docs-content code {
  font: .86em var(--mono);
  background: var(--bg-elev-2);
  border: 1px solid var(--line-soft);
  border-radius: 6px;
  padding: .1em .38em;
}

/* fenced code blocks */
.doc-code {
  margin: 0 0 18px;
  background: var(--bg-elev);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  overflow-x: auto;
  box-shadow: var(--shadow-sm);
}
.doc-code code {
  background: none; border: 0; padding: 0;
  font: .84rem/1.7 var(--mono);
  color: var(--fg);
  white-space: pre;
}

/* callouts */
.doc-callout {
  display: grid; grid-template-columns: auto 1fr; gap: 4px 14px;
  align-items: start;
  margin: 0 0 18px; padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--bg-elev);
}
.doc-callout p { margin: 0; color: var(--fg-muted); grid-column: 2; }
.doc-callout-label {
  grid-column: 2;
  font-family: var(--mono); font-size: .7rem;
  text-transform: uppercase; letter-spacing: .12em;
  color: var(--accent);
}
.doc-callout.tip { border-left-color: var(--accent-2); }
.doc-callout.tip .doc-callout-label { color: var(--accent-2); }
.doc-callout.warn { border-left-color: var(--danger); }
.doc-callout.warn .doc-callout-label { color: var(--danger); }

/* screenshots */
.doc-shot { margin: 6px 0 22px; }
.doc-shot img {
  display: block; width: 100%; height: auto; max-width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  background: var(--bg-elev);
}
.doc-shot figcaption {
  margin-top: 8px; font-size: .84rem; color: var(--fg-dim);
  font-family: var(--mono);
}

/* next steps */
.doc-next {
  margin-top: 40px; padding-top: 20px;
  border-top: 1px solid var(--line);
}
.doc-next-title {
  font-family: var(--mono); font-size: .72rem;
  text-transform: uppercase; letter-spacing: .12em; color: var(--fg-dim);
}
.doc-next ul { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 20px; }
.doc-next li a { color: var(--accent); font-weight: 600; }

/* /docs overview */
.docs-index-group { font-size: var(--text-lg); }
.docs-index-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: 14px; margin-bottom: 8px; }
.docs-index-card {
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px 18px;
  background: var(--bg-elev);
  transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.docs-index-card:hover { border-color: var(--accent-line); transform: translateY(-2px); box-shadow: var(--shadow); text-decoration: none; }
.docs-index-card h3 { margin: 0 0 4px; font-size: 1.02rem; color: var(--fg); }
.docs-index-card p { margin: 0; font-size: .9rem; }

/* mobile TOC toggle (hidden on desktop) */
.docs-toc-cb, .docs-toc-btn { display: none; }

@media (max-width: 900px) {
  .docs-shell { grid-template-columns: 1fr; gap: 0; }
  .docs-index-grid { grid-template-columns: 1fr; }
  .docs-toc-btn {
    display: inline-block;
    font-family: var(--mono); font-size: .82rem;
    padding: 9px 14px; margin-bottom: 18px;
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--bg-elev); color: var(--fg); cursor: pointer;
  }
  .docs-sidebar {
    position: static; max-height: none; overflow: visible;
    display: none;
    margin-bottom: 22px; padding-bottom: 18px;
    border-bottom: 1px solid var(--line-soft);
  }
  .docs-toc-cb:checked ~ .docs-sidebar { display: block; }
}

/* ---- legal & compliance + security/trust pages ----
   These reuse the docs shell (.docs-shell/.docs-sidebar/.docs-content) for
   chrome; the rules below cover only the legal-specific bits: the effective-date
   line, policy tables, and the security posture definition list. */
.legal-content h2 { margin-top: 32px; }
.legal-meta { color: var(--fg-dim); font-size: .86rem; margin: 0 0 20px; }
.legal-nowrap { white-space: nowrap; }
.legal-table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); margin: 0 0 18px; }
.legal-table { width: 100%; border-collapse: collapse; font-size: .9rem; }
.legal-table th {
  text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line);
  font: 700 .66rem var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--fg-dim);
}
.legal-table td { padding: 11px 14px; border-bottom: 1px solid var(--line-soft); vertical-align: top; color: var(--fg-muted); }
.legal-table tr:last-child td { border-bottom: 0; }
.legal-table td strong { color: var(--fg); }

.security-list { margin: 0 0 8px; }
.security-list dt { font-weight: 600; color: var(--fg); margin-top: 14px; }
.security-list dd { margin: 4px 0 0; color: var(--fg-muted); }
}

/* ==== css/pages/issues.css ==== */
@layer pages {
/* --- issues, labels & milestones ------------------------------------------- */

/* Label chips carry their own solid background + foreground so a label looks the
   same in light and dark themes (a label color is intrinsic, not theme-derived).
   The palette is fixed; keep these classes in sync with labelPalette (Go). */
.label-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; margin: 6px 0 2px; }
.label-chip {
  display: inline-flex; align-items: center; padding: 2px 10px;
  border-radius: var(--pill); font-size: .72rem; font-weight: 600;
  letter-spacing: .01em; line-height: 1.5; white-space: nowrap;
}
.pr-item-main .label-chips { margin: 4px 0 0; }

.lbl-b60205 { background: #b60205; color: #fff; }
.lbl-d73a4a { background: #d73a4a; color: #fff; }
.lbl-e99695 { background: #e99695; color: #1b1f23; }
.lbl-fbca04 { background: #fbca04; color: #1b1f23; }
.lbl-fef2c0 { background: #fef2c0; color: #1b1f23; }
.lbl-0e8a16 { background: #0e8a16; color: #fff; }
.lbl-c2e0c6 { background: #c2e0c6; color: #1b1f23; }
.lbl-006b75 { background: #006b75; color: #fff; }
.lbl-bfdadc { background: #bfdadc; color: #1b1f23; }
.lbl-1d76db { background: #1d76db; color: #fff; }
.lbl-0052cc { background: #0052cc; color: #fff; }
.lbl-5319e7 { background: #5319e7; color: #fff; }
.lbl-d4c5f9 { background: #d4c5f9; color: #1b1f23; }
.lbl-e4e669 { background: #e4e669; color: #1b1f23; }
.lbl-ededed { background: #ededed; color: #1b1f23; }
.lbl-f9d0c4 { background: #f9d0c4; color: #1b1f23; }

/* Color picker: a radiogroup of solid swatches; the checked one is ringed. */
.color-picker { display: flex; flex-wrap: wrap; gap: 8px; }
.color-swatch-label { display: inline-flex; cursor: pointer; }
.color-picker input { position: absolute; width: 1px; height: 1px; opacity: 0; }
.color-swatch { width: 22px; height: 22px; border-radius: 50%; display: inline-block; border: 1px solid var(--line); }
.color-picker input:checked + .color-swatch { outline: 2px solid var(--fg); outline-offset: 2px; }
.color-picker input:focus-visible + .color-swatch { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Label checkbox picker in the issue/MR sidebar. */
.label-picker { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 10px; }
.label-picker-row { display: flex; align-items: center; gap: 8px; cursor: pointer; }

/* Label management + inline edit dropdown. */
.label-new { margin-bottom: 20px; }
.label-edit { display: inline-block; }
.label-edit > summary { list-style: none; cursor: pointer; }
.label-edit > summary::-webkit-details-marker { display: none; }
.label-edit .form, .milestone-new .form { margin-top: 10px; padding: 12px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); }

/* Milestones list with native <progress> bars (no CSP-blocked inline styles). */
.milestone-new { margin-bottom: 20px; }
.milestone-new > summary { display: inline-flex; list-style: none; cursor: pointer; }
.milestone-new > summary::-webkit-details-marker { display: none; }
.milestone-list { list-style: none; margin: 0; padding: 0; }
.milestone-item { padding: 14px 0; border-bottom: 1px solid var(--line); }
.milestone-head { display: flex; align-items: baseline; gap: 10px; }
.milestone-title { font-weight: 700; font-size: 1rem; letter-spacing: -.01em; }
.milestone-progress { display: flex; align-items: center; gap: 10px; margin: 8px 0; }
.milestone-bar { width: 240px; max-width: 60%; height: 8px; border-radius: var(--pill); overflow: hidden; }
.milestone-bar::-webkit-progress-bar { background: var(--bg-inset, var(--bg-elev)); border-radius: var(--pill); }
.milestone-bar::-webkit-progress-value { background: var(--accent); border-radius: var(--pill); }
.milestone-bar::-moz-progress-bar { background: var(--accent); border-radius: var(--pill); }
.milestone-actions { display: inline-flex; gap: 8px; align-items: flex-start; margin-top: 6px; }
.issue-state-forms { display: none; }

/* .comment-tombstone, .comment-delete and the second .pr-comment-head moved to
   components/comment-thread.css — they style a shared component, and this page
   copy of .pr-comment-head sat in the `pages` layer where it outranked the
   real definition in pages/pr.css by file order alone. */
.btn-link { background: none; border: 0; padding: 0; font: inherit; color: var(--fg-muted); cursor: pointer; }
.btn-link:hover { color: var(--accent); }
.btn-link.danger:hover { color: var(--danger, var(--warn)); }
}

/* ==== css/pages/notifications.css ==== */
@layer pages {
.notif-page { max-width: 760px; margin: 0 auto; padding: 40px 20px 64px; }
.notif-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.notif-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap; margin: 6px 0 18px; padding-bottom: 10px; border-bottom: 1px solid var(--line);
}
.notif-settings-link { font-size: .88rem; color: var(--fg-muted); }
.notif-settings-link:hover { color: var(--accent); }

.notif-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.notif-item {
  display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px;
  border-top: 1px solid var(--line-soft); transition: background .12s;
}
.notif-item:first-child { border-top: none; }
.notif-item:hover { background: var(--bg-elev-2); }
.notif-item.unread { background: color-mix(in oklab, var(--accent) 6%, transparent); }
.notif-dot { flex: none; width: 8px; height: 8px; margin-top: 7px; border-radius: var(--pill); background: transparent; }
.notif-dot.on { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.notif-item-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.notif-item-title { font-weight: 600; color: var(--fg); line-height: 1.35; }
a.notif-item-title:hover { color: var(--accent); }
.notif-item-meta { font-size: .8rem; color: var(--fg-dim); display: flex; gap: 4px; flex-wrap: wrap; }
.notif-repo { font-family: var(--mono); color: var(--fg-muted); }

.notif-email-note { display: inline-block; margin: 0 0 18px; }
.notif-prefs { width: 100%; border-collapse: collapse; margin: 8px 0 22px; }
.notif-prefs th {
  text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line);
  font: 700 .66rem var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--fg-dim);
}
.notif-prefs td { padding: 12px 10px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.notif-prefs-toggle { width: 76px; text-align: center; }
.notif-prefs-label { display: block; font-weight: 600; color: var(--fg); }
.notif-prefs-desc { display: block; font-size: .84rem; color: var(--fg-muted); margin-top: 2px; }
}

/* ==== css/pages/style-guide.css ==== */
@layer pages {
/* ============================================================================
   /style-guide — superadmin review surface. All colour swatches use classes
   (never inline style) because the strict CSP blocks style attributes. Each
   .sg-chip-* paints one design token so the palette is visible in both themes.
   ============================================================================ */
.sg-page { padding: 40px 0 80px; }
.sg-lead { color: var(--fg-muted); max-width: 66ch; margin: 0 0 8px; }
.sg-section { margin: 40px 0; padding-top: 24px; border-top: 1px solid var(--line-soft); }
.sg-section:first-of-type { border-top: 0; padding-top: 0; }
.sg-section > h2 { font-size: var(--text-xl); margin: 0 0 4px; }
.sg-section > .sg-note { color: var(--fg-muted); font-size: .9rem; margin: 0 0 18px; }
.sg-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin: 0 0 14px; }
.sg-grid { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.sg-link { color: var(--accent); }
.sg-link:hover { text-decoration: underline; }
.sg-demo-box { border: 1px dashed var(--line); border-radius: var(--radius); padding: 12px; background: var(--bg-elev); color: var(--fg-muted); font-size: .85rem; text-align: center; }

.sg-swatches { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); }
.sg-swatch { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: var(--bg-elev); }
.sg-chip { display: block; height: 54px; border-bottom: 1px solid var(--line); }
.sg-swatch-meta { padding: 8px 10px; }
.sg-swatch-name { display: block; font: .78rem var(--mono); color: var(--fg); }
.sg-swatch-val { display: block; font: .72rem var(--mono); color: var(--fg-muted); }
.sg-chip-bg { background: var(--bg); }
.sg-chip-bg-elev { background: var(--bg-elev); }
.sg-chip-bg-elev-2 { background: var(--bg-elev-2); }
.sg-chip-line { background: var(--line); }
.sg-chip-fg { background: var(--fg); }
.sg-chip-fg-muted { background: var(--fg-muted); }
.sg-chip-fg-dim { background: var(--fg-dim); }
.sg-chip-accent { background: var(--accent); }
.sg-chip-accent-2 { background: var(--accent-2); }
.sg-chip-success { background: var(--success); }
.sg-chip-danger { background: var(--danger); }
.sg-chip-merge { background: var(--merge); }
.sg-chip-portal { background: var(--portal-1); }

/* ============================================================================
   /style-guide — the superadmin-only design-system reference surface.
   Purely ADDITIVE and namespaced (.sg-*): it documents the tokens/primitives
   defined above without altering any of them. Swatch/scale/spacing demos read
   the live custom properties through dedicated classes (the strict CSP blocks
   inline style="…"), so every sample tracks the active theme.
   ============================================================================ */
.sg-page { padding: 34px 0 96px; }
.sg-lead { color: var(--fg-muted); font-size: var(--text-lg); max-width: 70ch; margin: 6px 0 6px; }
.sg-caption { color: var(--fg-dim); font: .82rem var(--mono); margin: 0 0 8px; }

.sg-layout { display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: 44px; align-items: start; margin-top: 26px; }
@media (max-width: 860px) { .sg-layout { grid-template-columns: 1fr; gap: 0; } }
.sg-toc { position: sticky; top: 84px; align-self: start; display: flex; flex-direction: column; gap: 1px; font-size: .86rem; max-height: calc(100vh - 108px); overflow-y: auto; }
.sg-toc-title { font: 700 .7rem var(--mono); text-transform: uppercase; letter-spacing: .13em; color: var(--fg-dim); margin: 0 0 8px 10px; }
.sg-toc a { padding: 5px 10px; border-radius: var(--radius-sm); color: var(--fg-muted); }
.sg-toc a:hover { background: var(--bg-elev); color: var(--fg); }
@media (max-width: 860px) { .sg-toc { position: static; max-height: none; flex-direction: row; flex-wrap: wrap; gap: 4px; margin-bottom: 22px; } }

.sg-main { min-width: 0; }
.sg-section { padding-top: 30px; margin-top: 30px; border-top: 1px solid var(--line-soft); scroll-margin-top: 80px; }
.sg-section:first-child { padding-top: 0; margin-top: 8px; border-top: 0; }
.sg-section > h2 { font-size: var(--text-xl); letter-spacing: -.02em; margin: 0 0 4px; }
.sg-section-note { color: var(--fg-muted); font-size: .92rem; margin: 0 0 18px; max-width: 72ch; }
.sg-section-note code { font-family: var(--mono); font-size: .86em; background: var(--bg-elev-2); padding: .1em .4em; border-radius: 5px; }
.sg-sub { font: 700 .7rem var(--mono); text-transform: uppercase; letter-spacing: .1em; color: var(--fg-dim); margin: 24px 0 10px; }
.sg-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin: 0 0 12px; }
.sg-stack { display: flex; flex-direction: column; gap: 12px; margin: 0 0 12px; }
.sg-grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; align-items: start; }
.sg-panel { border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); padding: 18px; }
.sg-demo-box { border: 1px dashed var(--line); border-radius: var(--radius-sm); padding: 16px; text-align: center; color: var(--fg-dim); font: .8rem var(--mono); background: var(--bg-elev); }

/* palette swatches */
.sg-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.sg-swatch { border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden; background: var(--bg-elev); }
.sg-swatch-chip { display: block; height: 54px; border-bottom: 1px solid var(--line-soft); }
.sg-swatch-meta { display: flex; flex-direction: column; gap: 2px; padding: 8px 10px; }
.sg-swatch-name { font: 600 .78rem var(--mono); color: var(--fg); }
.sg-swatch-val { font: .72rem var(--mono); color: var(--fg-dim); }
/* token fills — live custom properties, CSP-safe */
.sg-tok-bg { background: var(--bg); }
.sg-tok-bg-elev { background: var(--bg-elev); }
.sg-tok-bg-elev-2 { background: var(--bg-elev-2); }
.sg-tok-line { background: var(--line); }
.sg-tok-fg { background: var(--fg); }
.sg-tok-fg-muted { background: var(--fg-muted); }
.sg-tok-fg-dim { background: var(--fg-dim); }
.sg-tok-accent { background: var(--accent); }
.sg-tok-accent-2 { background: var(--accent-2); }
.sg-tok-danger { background: var(--danger); }
.sg-tok-warn { background: var(--warn); }
.sg-tok-merge { background: var(--merge); }
.sg-tok-portal { background: conic-gradient(from 0deg, var(--portal-2), var(--portal-1), var(--portal-3), var(--portal-2)); }

/* type scale */
.sg-type-row { display: flex; align-items: baseline; gap: 18px; padding: 8px 0; border-bottom: 1px solid var(--line-soft); }
.sg-type-tag { flex: none; width: 8.5rem; font: .74rem var(--mono); color: var(--fg-dim); }
.sg-type-sample { color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.sg-fs-xs { font-size: var(--text-xs); }
.sg-fs-sm { font-size: var(--text-sm); }
.sg-fs-base { font-size: var(--text-base); }
.sg-fs-lg { font-size: var(--text-lg); }
.sg-fs-xl { font-size: var(--text-xl); }
.sg-fs-2xl { font-size: var(--text-2xl); }
.sg-fs-3xl { font-size: var(--text-3xl); }

/* spacing rhythm */
.sg-space-row { display: flex; align-items: center; gap: 14px; padding: 5px 0; }
.sg-space-tag { flex: none; width: 5rem; font: .74rem var(--mono); color: var(--fg-dim); }
.sg-space-bar { height: 14px; border-radius: 3px; background: var(--accent-soft); border: 1px solid var(--accent-line); }
.sg-sp-1 { width: var(--sp-1); }
.sg-sp-2 { width: var(--sp-2); }
.sg-sp-3 { width: var(--sp-3); }
.sg-sp-4 { width: var(--sp-4); }
.sg-sp-5 { width: var(--sp-5); }
.sg-sp-6 { width: var(--sp-6); }
.sg-sp-7 { width: var(--sp-7); }
.sg-sp-8 { width: var(--sp-8); }

/* radii + elevation demos */
.sg-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px; }
.sg-demo-tile { display: grid; place-items: center; height: 68px; background: var(--bg-elev-2); border: 1px solid var(--line); color: var(--fg-dim); font: .72rem var(--mono); }
.sg-r-sm { border-radius: var(--radius-sm); }
.sg-r { border-radius: var(--radius); }
.sg-r-lg { border-radius: var(--radius-lg); }
.sg-r-pill { border-radius: var(--pill); }
.sg-elev-tile { display: grid; place-items: center; height: 68px; border-radius: var(--radius); background: var(--bg-elev); border: 1px solid var(--line-soft); color: var(--fg-dim); font: .72rem var(--mono); }
.sg-sh-sm { box-shadow: var(--shadow-sm); }
.sg-sh { box-shadow: var(--shadow); }
.sg-sh-lg { box-shadow: var(--shadow-lg); }
.sg-sh-glow { box-shadow: var(--glow); }

/* States catalogue (section 11) — one framed card per case, each labelled with
   the component it renders so a reader leaves with a call to copy rather than a
   screenshot to reimplement. Two columns where there is room; the cases contain
   full-width components (an error card, a clone widget), so the frame never
   constrains them below their own minimum. */
.sg-cases { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)); gap: 18px; }
.sg-case { display: flex; flex-direction: column; gap: 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-elev); padding: 14px; min-width: 0; }
.sg-case-head { display: flex; flex-direction: column; gap: 3px; }
.sg-case-name { font: 700 .8rem var(--mono); color: var(--fg); }
.sg-case-where { font: .72rem var(--mono); color: var(--fg-dim); overflow-wrap: anywhere; }
.sg-case-body { min-width: 0; }
/* The banner variants are full-bleed page chrome; inside a card they need their
   own edge, and the error card's centred min-height would swallow the grid. */
.sg-case-body > .banner { border-radius: var(--radius-sm); border: 1px solid var(--line); }
.sg-case-body > .error-card { max-width: none; padding: 24px 20px; box-shadow: none; }

/* side-by-side vertical-nav demo (settings + docs) */
.sg-vnav-demo { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 24px; align-items: start; }
.sg-vnav-demo .settings-nav { min-width: 0; }
.sg-vnav-demo .docs-sidebar { position: static; max-height: none; top: auto; }
}

/* ==== css/overrides.css ==== */
@layer overrides {
/* ---- footer clearance: the last block on ANY page keeps 60px clear of the
   footer rule, so content never butts against it. Three page shells did this
   individually (.explorer, .repo-layout, .commit-list) and every page added
   since got it only if someone remembered; stating it once as a page-shell
   rule is what makes it a convention instead of a habit. The two shells that
   already own their bottom spacing opt out rather than doubling it. ---- */
main#main > .wrap > *:last-child { margin-bottom: 60px; }
main#main > .wrap > .explorer:last-child,
main#main > .wrap > .repo-layout:last-child { margin-bottom: 0; }

/* ---- reduced motion: still, but never broken ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
  .portal { animation: none; }
  ::view-transition-old(root), ::view-transition-new(root) { animation: none; }
}

/* ---- container queries: components reflow by their OWN width, not the viewport,
   so the same card/panel works in a wide main column or a narrow sidebar. ---- */
.repo-side, .dash-side, .profile-side, .prd-side { container: side / inline-size; }

@container side (max-width: 260px) {
  .release-card-head, .release-card-meta { flex-direction: column; align-items: flex-start; gap: 4px; }
  .side-panel { padding: 12px; }
}

/* CI run list: each run card stacks its when/meta under the title in a narrow slot. */
.ci-run-list { container: ci-runs / inline-size; }
@container ci-runs (max-width: 34rem) {
  .ci-run-item { flex-wrap: wrap; }
  .ci-run-when { width: 100%; margin-left: 0; }
}

/* The run dependency graph already scrolls in its own overflow-x box; give the
   queue chart + capacity panels a container so internal reflow keys off their
   own width rather than the page. */
.cap-chart, .cap-panel { container-type: inline-size; }

/* ---- touch targets: >= 40px on coarse (touch) pointers, without bulking the
   desktop density. Applies to controls that are already flex/grid boxes. ---- */
@media (pointer: coarse) {
  .btn, .theme-toggle, .nav-toggle, .notif-bell, .clone-copy { min-height: 40px; }
}
}

