/* =============================================================================
   0. Self-hosted brand fonts (2026-08-05)
   -----------------------------------------------------------------------------
   The three families used to load from fonts.googleapis.com on every page.
   layouts/main backs the PUBLIC pages (/card, /legal/*, /email/unsubscribe,
   the error pages), so every visitor's IP reached Google with no consent step
   — on a site that deliberately ships no cookie banner, from an EU controller.
   Serving them ourselves removes the third party entirely (and one DNS +
   TLS + round-trip from first paint).

   VARIABLE fonts: one file per family per subset covers every weight the
   design system uses (Outfit 400-800, the other two 400-700) — 6 files,
   ~128 KB total, instead of 13 static weights.

   latin-ext is NOT optional: Slovak needs č ď ľ ĺ ň ŕ š ť ž ä, and the
   company address + legal documents are Slovak. The unicode-range split is
   Google's own, so a browser only downloads the subset a page actually uses.

   Licence: all three are SIL Open Font License 1.1 (Outfit, Instrument Sans,
   JetBrains Mono) — self-hosting and redistribution are expressly permitted.
   Files live in public/fonts/; re-fetch with the same variable ranges if a
   family is ever updated. These URLs carry no ?v= stamp and are served
   immutable for a year in production, so REPLACE means RENAME — a new file
   name, not a new file at the same name.
   ============================================================================= */
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/outfit-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/outfit-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/instrument-sans-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/instrument-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* =============================================================================
   Playlytix Portal — Design System (single source of truth)
   "Vivid minimal" direction: spacious, high-contrast, rounded surfaces.
   Cyan→blue brand primary with violet + magenta secondaries; data-viz leans
   on all three. Fonts: Outfit (display) + Plus Jakarta Sans (body).
   Add new styles at the END of the relevant section. Never reorder sections.
   Use the CSS variables — never hardcode a colour/spacing that duplicates one.

   1.  Tokens (:root)
   2.  Reset & base
   3.  Typography & links
   4.  App shell (sidebar + main)
   5.  Sidebar nav (brand logo + items)
   6.  Topbar
   7.  Buttons
   8.  Cards
   9.  Stat / KPI cards
   10. Tables
   11. Forms & inputs
   12. Chip multi-selects
   13. Toggle switches
   14. Badges & status pills
   15. Alerts
   16. Profile completion ring
   17. Dashboard (tester)
   18. Profile page
   19. Settings page
   20. Admin dashboard
   21. Users page
   22. Tests page (card grid)
   23. Form builder
   24. Assignments page
   25. Developer space
   26. Feedback form (star rating)
   27. Error pages
   28. File attachments
   29. Utilities & responsive
   ========================================================================== */

/* 1. Tokens ----------------------------------------------------------------- */
/* "Scoreboard" system: true-black neutral ground, hairline structure, sharp
   corners, NO gradients/glows. Three color voices, each with ONE meaning:
     cyan   = act   (primary buttons, links, selection, focus)
     magenta = live (kickers, live counts, "new" markers)
     green  = money (rewards, earnings) — also the "done" status colour
   Status = desaturated green/amber/red as dot + word, never filled pills.
   NOTE: the *-gradient vars are kept for compatibility but hold FLAT colors
   now — anything still referencing them renders solid. Same for --glow-cyan
   (transparent). Do not reintroduce gradients/glows in new sections. */
:root {
  /* Backgrounds — true-black neutrals (no blue cast) */
  --bg-base: #0A0B0D;
  --bg-surface: #101214;
  --bg-card: #101214;
  --bg-elevated: #16191D;

  /* Brand — cyan means "act here" */
  --primary: #2EE6FF;
  --primary-dark: #17B8D4;
  --primary-ink: #04252B;            /* text color ON a cyan fill */
  --primary-gradient: #2EE6FF;       /* flat now (compat) */
  --secondary-gradient: #2EE6FF;     /* flat now (compat) */
  --primary-glow: rgba(46, 230, 255, 0.16);
  --primary-bg: rgba(46, 230, 255, 0.08);

  /* Voices — magenta = live/highlight, green = money (see --success) */
  --violet: #8B5CF6;                 /* legacy; avoid in new work */
  --violet-gradient: #8B5CF6;        /* flat now (compat) */
  --violet-bg: rgba(139, 92, 246, 0.13);
  --magenta: #FF5CA5;
  --magenta-gradient: #FF5CA5;       /* flat now (compat) */
  --magenta-bg: rgba(255, 92, 165, 0.10);

  /* Surfaces — glass overlay retired (flat now, compat) */
  --glass: none;

  /* Borders — neutral hairlines; colour is reserved for true accents */
  --border-default: rgba(255, 255, 255, 0.09);
  --border-strong: rgba(255, 255, 255, 0.16);
  --border-accent: rgba(46, 230, 255, 0.45);

  /* Text — neutral grays, no blue cast */
  --text-primary: #F2F4F5;
  --text-secondary: #9AA1A9;
  /* Raised #5E656D -> #7A828C (2026-08-07, review round 3 / WCAG): the old
     value hit ~3:1 on the surfaces (2.99 on elevated — failing even the
     large-text floor) while carrying small essential text in ~120 places.
     This value clears 4.5:1 on all three grounds (4.53 on elevated) while
     staying clearly quieter than --text-secondary. Designer should eyeball
     the overall balance on QA. */
  --text-muted: #7A828C;
  --link: #2EE6FF;

  /* Status — desaturated; green doubles as the money voice */
  --success: #43D98F;
  --warning: #E8B04B;
  --error: #E87070;
  --success-bg: rgba(67, 217, 143, 0.10);
  --warning-bg: rgba(232, 176, 75, 0.10);
  --error-bg: rgba(232, 112, 112, 0.10);

  /* Spacing scale */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;
  --space-8: 48px;

  /* Radius — sharp, broadcast-scoreboard corners */
  --radius-sm: 4px;
  --radius: 6px;
  --radius-lg: 8px;
  --radius-pill: 999px;

  /* Shadows — depth only, never glow */
  --shadow: 0 16px 44px rgba(0, 0, 0, 0.45);
  --shadow-sm: 0 6px 18px rgba(0, 0, 0, 0.32);
  --glow-cyan: 0 0 0 rgba(0, 0, 0, 0);  /* retired (compat) */

  /* Layout */
  --sidebar-width: 256px;
  --topbar-height: 70px;

  /* Fonts — three text voices: sentences (body), headings (logo),
     data + uppercase labels (mono). Never mix voices within one element. */
  --font-logo: 'Outfit', system-ui, sans-serif;
  --font-body: 'Instrument Sans', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', Consolas, monospace;
}

/* 2. Reset & base ----------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
/* Make the `hidden` attribute authoritative — component `display` rules (e.g.
   .btn) would otherwise override the UA [hidden] { display:none } and leave
   "hidden" elements visible. */
[hidden] { display: none !important; }
html, body { height: 100%; }
html {
  /* 17px (designer rounds 2026-07-26; was 15px): the ROOT is the global
     legibility lever — every rem in this file resolves against it. It must
     live on html, not body: a body font-size only reaches text that
     inherits, while rem-sized elements (most of the UI) ignore it — which
     is exactly the bug round 1 shipped and round 2 caught. */
  font-size: 17px;
}
body {
  margin: 0;
  background: var(--bg-base);
  color: var(--text-primary);
  font-family: var(--font-body);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, svg { display: block; max-width: 100%; }
button { font-family: inherit; }
::selection { background: rgba(46, 230, 255, 0.25); color: #fff; }

/* 3. Typography & links ----------------------------------------------------- */
h1, h2, h3, h4 { font-family: var(--font-logo); font-weight: 600; line-height: 1.18; letter-spacing: -0.02em; margin: 0 0 var(--space-3); color: var(--text-primary); }
h1 { font-size: 1.9rem; }
h2 { font-size: 1.4rem; }
h3 { font-size: 1.1rem; }
p { margin: 0 0 var(--space-3); color: var(--text-secondary); }
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }
.muted { color: var(--text-muted); }

/* 4. App shell -------------------------------------------------------------- */
.app-shell { display: flex; min-height: 100vh; }
.sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  background: rgba(13, 14, 17, 0.7);
  border-right: 1px solid var(--border-default);
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  backdrop-filter: blur(12px);
}
.main-area { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.content { padding: var(--space-8) var(--space-6); max-width: 1180px; width: 100%; margin: 0 auto; }

/* 5. Sidebar nav ------------------------------------------------------------ */
.sidebar-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-6) var(--space-5) var(--space-5);
}
/* Clean logo asset + CSS-controlled glow (subtle here; stronger on login). */
.sidebar-brand .brand-mark { height: 34px; width: auto; filter: drop-shadow(0 0 8px rgba(46, 230, 255, 0.35)); }
.sidebar-brand .brand-wordmark { height: 15px; width: auto; opacity: 0.95; }

/* Role label as a section kicker (uppercase mono, magenta — same voice as the
   page kickers) — the old cyan pill misused the act voice: a role is identity,
   not an action. */
.sidebar-role {
  margin: 0 var(--space-5) var(--space-5);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em; /* live-voice tracking, like .kicker — not the quiet ramp */
  color: var(--magenta);
}
.sidebar-nav { display: flex; flex-direction: column; gap: 3px; padding: 0 var(--space-3); flex: 1; overflow-y: auto; }
/* Nav items speak the same uppercase-mono label voice as the tester topnav
   links; active = cyan rail + tint (cyan = selection), mirroring the tester's
   cyan underline instead of a filled block. */
.nav-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  color: var(--text-secondary);
  font-family: var(--font-mono);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
}
.nav-item:hover { background: rgba(255, 255, 255, 0.04); color: var(--text-primary); text-decoration: none; }
.nav-item.active {
  background: var(--primary-bg);
  color: var(--text-primary);
  box-shadow: inset 2px 0 0 var(--primary);
  font-weight: 700;
}
.nav-item.active:hover { background: var(--primary-bg); color: var(--text-primary); }
.nav-item.active .nav-icon { color: var(--primary); opacity: 1; }
.nav-item .nav-icon { width: 20px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; opacity: 0.9; }
.nav-item .nav-icon svg { width: 17px; height: 17px; }
.mobile-nav-toggle svg { width: 22px; height: 22px; }
.sidebar-footer {
  border-top: 1px solid var(--border-default);
  padding: var(--space-4) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.sidebar-user { font-size: 0.82rem; }
.sidebar-user .name { font-weight: 600; color: var(--text-primary); }
.sidebar-user .email { color: var(--text-muted); word-break: break-all; }
/* Grouped nav: mono section labels + housekeeping links (Changelog/Settings)
   pinned to the bottom of the rail, above the user footer. */
.nav-group-label {
  margin: var(--space-5) var(--space-4) var(--space-1);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
}
.sidebar-nav-bottom {
  margin-top: auto;
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-default);
  display: flex;
  flex-direction: column;
  gap: 3px;
}
/* Footer user row — the SAME avatar + name treatment as the tester topnav
   (.topnav-av, §37), so every role sees one identity style. */
.sidebar-user { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.sidebar-user-id { min-width: 0; }
.sidebar-user-id .name,
.sidebar-user-id .email { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar-footer .topnav-out { width: 100%; }

/* 6. Topbar ----------------------------------------------------------------- */
.topbar {
  height: var(--topbar-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--space-6);
  border-bottom: 1px solid var(--border-default);
  background: rgba(10, 11, 13, 0.82);
  backdrop-filter: blur(12px);
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar-title { font-family: var(--font-logo); font-size: 1.05rem; font-weight: 600; letter-spacing: -0.01em; }
.topbar-right { display: flex; align-items: center; gap: var(--space-4); }
.mobile-nav-toggle { display: none; background: none; border: none; color: var(--text-primary); font-size: 1.4rem; cursor: pointer; }

/* 7. Buttons ---------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-primary);
  font-weight: 600;
  font-size: 0.88rem;
  line-height: 1.35; /* normalize <a> vs <button> so mixed rows are equal height */
  cursor: pointer;
  transition: transform 0.08s, box-shadow 0.18s, background 0.15s, border-color 0.15s, opacity 0.15s;
  text-decoration: none;
  white-space: nowrap;
}
.btn:hover { text-decoration: none; background: rgba(255, 255, 255, 0.06); border-color: var(--border-strong); }
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn-primary {
  background: var(--primary);
  color: var(--primary-ink);
  border: none;
}
.btn-primary:hover { background: var(--primary); transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.btn-secondary { background: var(--bg-elevated); border-color: var(--border-default); }
.btn-ghost { background: transparent; border-color: var(--border-default); }
.btn-danger { background: var(--error-bg); border-color: rgba(251, 113, 133, 0.35); color: #fda4af; }
.btn-success { background: var(--success-bg); border-color: rgba(52, 211, 153, 0.35); color: #86e3bf; }
.btn-sm { padding: var(--space-2) var(--space-3); font-size: 0.8rem; }
.btn-block { width: 100%; }
/* Busy state — the global double-submit guard (main.js) adds .is-loading when a
   form submits; pages may also set wait text (e.g. "Dispatching…"). The ring
   inherits currentColor so it reads on primary and ghost buttons alike. The
   global reduced-motion block freezes the spin; the label carries the meaning. */
.btn.is-loading { pointer-events: none; }
.btn.is-loading::before {
  content: '';
  width: 0.85em;
  height: 0.85em;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  border: 2px solid currentColor;
  border-top-color: transparent;
  animation: btn-spin 0.7s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* 8. Cards ------------------------------------------------------------------ */
.card {
  background: var(--glass), var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.card + .card { margin-top: var(--space-5); }
.card-header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-5) var(--space-5) var(--space-4);
  border-bottom: 1px solid var(--border-default);
}
.card-title { margin: 0; font-size: 1.05rem; }
.card-body { padding: var(--space-5); }

/* 9. Stat / KPI cards ------------------------------------------------------- */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: var(--space-4); }
.stat-card {
  background: var(--glass), var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}
.stat-label { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; }
.stat-value { font-family: var(--font-mono); font-size: 2.1rem; font-weight: 700; letter-spacing: -0.02em; margin-top: var(--space-2); }
/* Enriched KPI tile (icon header, accent + tone variants, hover lift). */
.stat-card { transition: border-color 0.15s ease, transform 0.12s ease, box-shadow 0.2s ease; }
.stat-card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--shadow); }
.stat-card-head { display: flex; align-items: center; gap: var(--space-3); }
.stat-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex-shrink: 0;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border-default);
  color: var(--text-secondary);
}
.stat-icon svg { width: 16px; height: 16px; }
.stat-card.is-accent { border-color: var(--border-accent); background: var(--primary-bg), var(--bg-card); }
.stat-card.is-accent .stat-icon { background: var(--primary-bg); border-color: var(--border-accent); color: var(--primary); }
.stat-card.tone-danger .stat-icon { background: var(--error-bg); border-color: rgba(251, 113, 133, 0.32); color: var(--error); }
.stat-card.tone-danger .stat-value { color: var(--error); }

/* Scoreboard KPI tiles — neutral hairline icon badges (the old cyan/violet/
   magenta position-rotation is retired: colour must MEAN something, and a
   tile's position isn't meaning). Explicit tones (accent/danger) still win. */
.stat-icon { width: 42px; height: 42px; border-radius: var(--radius); }
.stat-icon svg { width: 20px; height: 20px; }
.stat-value { font-size: 2.3rem; margin-top: var(--space-4); font-variant-numeric: tabular-nums; }
.stat-grid > .stat-card.is-accent .stat-icon,
.kpi-grid > .stat-card.is-accent .stat-icon { background: var(--primary-bg); border: 1px solid var(--border-accent); color: var(--primary); }
.stat-grid > .stat-card.tone-danger .stat-icon,
.kpi-grid > .stat-card.tone-danger .stat-icon { background: var(--error-bg); border: 1px solid rgba(232, 112, 112, 0.35); color: var(--error); }

/* A KPI tile can be a link to the page behind its number (admin dashboard).
   The anchor variant keeps the tile look; the existing hover lift signals it. */
a.stat-card { display: block; color: inherit; text-decoration: none; }

/* 10. Tables ---------------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.table th, .table td { padding: var(--space-3) var(--space-4); text-align: left; }
.table thead th {
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border-bottom: 1px solid var(--border-default);
}
.table tbody tr { border-bottom: 1px solid var(--border-default); }
.table tbody tr:hover { background: rgba(255, 255, 255, 0.025); }
.table tbody tr:last-child { border-bottom: none; }
.table thead th { background: rgba(255, 255, 255, 0.015); }
.table td { vertical-align: middle; }
.table-meta { font-size: 0.8rem; margin-bottom: var(--space-3); }

/* Compact identity cell (avatar + email), reused across admin tables. */
.user-cell { display: inline-flex; align-items: center; gap: var(--space-3); }
.user-cell-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex-shrink: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border-default);
  color: var(--text-secondary); font-family: var(--font-logo); font-weight: 700; font-size: 0.8rem;
}
.user-cell-id { display: inline-flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.user-cell-email { font-weight: 500; }
.user-cell-sub { font-size: 0.78rem; color: var(--text-muted); word-break: break-all; }

/* Segmented control (filter tabs). */
.segmented {
  display: inline-flex; gap: 4px; padding: 4px;
  background: var(--bg-elevated); border: 1px solid var(--border-default); border-radius: var(--radius);
}
.segmented a {
  padding: 6px 16px; border-radius: var(--radius-sm);
  font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); text-decoration: none;
  transition: color 0.15s ease, background 0.15s ease;
}
.segmented a:hover { color: var(--text-primary); }
.segmented a.active { background: var(--primary); color: var(--primary-ink); }

/* Spacious, low-chrome tables (no header fill; roomier rows). */
.table th, .table td { padding: var(--space-4); }
.table thead th { background: transparent; }
.table tbody tr:hover { background: rgba(255, 255, 255, 0.035); }

/* 11. Forms & inputs -------------------------------------------------------- */
.form-group { margin-bottom: var(--space-4); }
.form-group > label { display: block; margin-bottom: var(--space-2); font-weight: 500; font-size: 0.88rem; color: var(--text-secondary); }input[type="text"], input[type="email"], input[type="password"], input[type="url"], input[type="number"], input[type="search"], input[type="datetime-local"], input[type="date"], select, textarea {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 0.92rem;
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}input::placeholder, textarea::placeholder { color: var(--text-muted); }input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--border-accent);
  box-shadow: 0 0 0 3px var(--primary-bg);
  background: rgba(255, 255, 255, 0.03);
}
textarea { min-height: 120px; resize: vertical; }
select { appearance: none; background-image: linear-gradient(45deg, transparent 50%, var(--text-muted) 50%), linear-gradient(135deg, var(--text-muted) 50%, transparent 50%); background-position: calc(100% - 18px) 55%, calc(100% - 13px) 55%; background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
input[type="datetime-local"], input[type="date"], select { color-scheme: dark; }
option { background-color: var(--bg-elevated); color: var(--text-primary); }
.form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-4); }
.form-section { margin-bottom: var(--space-6); }
.form-section-title { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); margin-bottom: var(--space-4); padding-bottom: var(--space-2); border-bottom: 1px solid var(--border-default); }
.form-hint { color: var(--text-muted); font-size: 0.8rem; margin-top: var(--space-1); }
.form-actions { display: flex; gap: var(--space-3); margin-top: var(--space-6); }

/* 12. Chip multi-selects ---------------------------------------------------- */
.chip-group { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.chip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.02);
  color: var(--text-secondary);
  font-size: 0.85rem;
  cursor: pointer;
  user-select: none;
  transition: all 0.15s;
}
.chip input { position: absolute; opacity: 0; pointer-events: none; }
.chip:hover { border-color: var(--border-strong); color: var(--text-primary); }
.chip:has(input:checked) {
  background: var(--primary-bg);
  border-color: var(--border-accent);
  color: var(--primary);
}

/* 13. Toggle switches ------------------------------------------------------- */
.switch-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); padding: var(--space-3) 0; border-bottom: 1px solid var(--border-default); }
.switch-row:last-child { border-bottom: none; }
.switch { position: relative; display: inline-block; width: 46px; height: 26px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.slider {
  position: absolute; inset: 0; cursor: pointer;
  background: var(--bg-elevated);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  transition: 0.2s;
}
.slider::before {
  content: ''; position: absolute; height: 18px; width: 18px; left: 3px; top: 3px;
  background: var(--text-secondary); border-radius: 50%; transition: 0.2s;
}
.switch input:checked + .slider { background: var(--primary-gradient); border-color: transparent; box-shadow: var(--glow-cyan); }
.switch input:checked + .slider::before { transform: translateX(20px); background: var(--bg-base); }

/* 14. Badges & status pills ------------------------------------------------- */
/* Status = DOT + WORD (scoreboard voice): no filled pills. The dot carries
   the colour, the word carries the meaning, mono-uppercase carries the tone. */
.badge {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 0.72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-secondary);
  white-space: nowrap;
}
.badge::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--text-muted); flex-shrink: 0; }
.badge-success::before, .status-completed::before, .status-paid::before { background: var(--success); }
.badge-warning::before, .status-assigned::before, .status-pending::before { background: var(--warning); }
.badge-danger::before, .status-declined::before, .status-overdue::before, .badge-overdue::before { background: var(--error); }
.badge-info::before, .status-accepted::before { background: var(--primary); }
.status-declined { opacity: 0.8; }
/* Forfeited payout (payee deleted their account): terminal but NOT an error —
   it keeps the muted default dot from .badge::before, unlike .status-declined. */
.status-forfeited { opacity: 0.8; }
/* Category/identity tags (roles etc.) — bordered, NOT a status: use .tag
   when the label names what something IS, not its state. (`.chip` is taken
   by the profile multi-selects in §12.) */
.tag {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: 2px 10px;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: 0.72rem; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.06em;
  border: 1px solid var(--border-default);
  background: rgba(255, 255, 255, 0.03);
  color: var(--text-secondary);
  white-space: nowrap;
}

/* 15. Alerts ---------------------------------------------------------------- */
.alert {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius);
  border: 1px solid var(--border-default);
  margin-bottom: var(--space-5);
  font-size: 0.9rem;
}
.alert-success { background: var(--success-bg); border-color: rgba(52, 211, 153, 0.3); color: #a7f3d0; }
.alert-error { background: var(--error-bg); border-color: rgba(251, 113, 133, 0.35); color: #fecdd3; }
.alert-info { background: var(--primary-bg); border-color: var(--border-accent); color: var(--primary); }
.alert-close { background: none; border: none; color: inherit; font-size: 1.2rem; cursor: pointer; line-height: 1; opacity: 0.7; }
.alert-close:hover { opacity: 1; }
.alert-warning { background: rgba(245, 196, 81, 0.10); border-color: rgba(245, 196, 81, 0.30); color: #fde68a; }
/* A notice on its way out (main.js: closed, or a transient one after 7 s) — under reduced motion it is removed outright. */
.alert.is-leaving { opacity: 0; transition: opacity 0.3s ease; }
/* The feedback form while a submit is in flight (2026-09-11): every control is
   disabled by the script; the drop zones read inert too. */
form.is-submitting .file-drop { pointer-events: none; opacity: 0.6; }
/* A banner that carries an ACTION button (T&C update, away, profile nudge)
   stacks on small screens — side-by-side squeezed its text to half width at
   375px. Plain text + close-X alerts keep the row. */
@media (max-width: 480px) {
  .alert:has(.btn) { flex-direction: column; align-items: flex-start; }
}

/* 16. Profile completion ring ----------------------------------------------- */
.progress-ring { position: relative; width: 116px; height: 116px; flex-shrink: 0; }
.progress-ring svg { transform: rotate(-90deg); }
.progress-ring .ring-bg { stroke: rgba(255, 255, 255, 0.07); }
.progress-ring .ring-fg { stroke: var(--primary); stroke-linecap: round; transition: stroke-dashoffset 0.6s ease; } /* flat cyan since 2026-09-02 — the last gradient in the portal */
.progress-ring .ring-label {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--font-logo); font-weight: 700; font-size: 1.5rem;
}

/* 17. Dashboard (tester) ---------------------------------------------------- */
.section-title { display: flex; align-items: center; justify-content: space-between; margin: var(--space-8) 0 var(--space-4); }
.assignment-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  background: var(--glass), var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  transition: border-color 0.15s;
}
.assignment-row:hover { border-color: var(--border-strong); }
.assignment-row.is-declined { opacity: 0.55; }
.assignment-actions { display: flex; gap: var(--space-2); align-items: center; }
.inline-form { display: inline; }

/* Page header (greeting + subtitle + optional action). */
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-5); }
.page-title { font-size: 1.5rem; margin: 0 0 var(--space-1); }
.page-sub { color: var(--text-muted); margin: 0; font-size: 0.92rem; }
@media (max-width: 900px) { .dash-overview { grid-template-columns: 1fr; } .completion-card .ring-wrap { flex-direction: row; text-align: left; } }

/* Assignment rows: left status bar + hover lift. */
.assignment-row { position: relative; overflow: hidden; padding-left: calc(var(--space-5) + 6px); transition: border-color 0.15s ease, transform 0.12s ease; }
.assignment-row:hover { transform: translateY(-1px); }
.assignment-row.is-muted { opacity: 0.55; }

/* 18. Profile page ---------------------------------------------------------- */
/* Aside capped (was 2fr/1fr): at the wide shell a fractional aside ballooned
   past what a TOC + ring needs; the form takes the remainder. */
.profile-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(280px, 340px); gap: var(--space-5); align-items: start; }
@media (max-width: 900px) { .profile-grid { grid-template-columns: 1fr; } }

/* Stacked section cards, each with an icon header. */
.profile-main { display: flex; flex-direction: column; gap: var(--space-5); }
.profile-section .section-head {
  display: flex; align-items: flex-start; gap: var(--space-3);
  margin-bottom: var(--space-5); padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--border-default);
}
.profile-section .section-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex-shrink: 0;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--border-default);
  color: var(--text-secondary);
}
.profile-section .section-ico svg { width: 19px; height: 19px; }
.profile-section .section-name { margin: 0; font-size: 1.05rem; }
.profile-section .section-desc { margin: 2px 0 0; color: var(--text-muted); font-size: 0.85rem; }
.profile-section .form-group:last-child,
.profile-section .switch-row:last-child { margin-bottom: 0; }

/* Sticky completion sidebar. Offset clears the fixed topnav (~64px); since the
   aside grew the section TOC (2026-07-13) it also scrolls internally instead of
   overflowing short viewports. */
.profile-aside { position: sticky; top: 84px; max-height: calc(100vh - 104px); overflow-y: auto; }
.profile-aside .card-body { text-align: center; }
.profile-aside .aside-ring { display: flex; justify-content: center; margin-bottom: var(--space-4); }
/* Match the ring container to this page's 104px SVG so the % label centres. */
.profile-aside .progress-ring { width: 104px; height: 104px; }
.profile-aside .aside-title { margin: 0 0 var(--space-2); font-size: 1.05rem; }
.profile-aside .aside-desc { color: var(--text-secondary); font-size: 0.88rem; margin: 0 0 var(--space-5); }
.profile-aside .aside-cancel { margin-top: var(--space-2); }
@media (max-width: 900px) { .profile-aside { position: static; } }
/* Gaming IDs — one platform per row, tiny group labels. */
.id-platform { padding-top: var(--space-4); }
.id-platform:first-of-type { padding-top: 0; }
.id-platform-label { display: block; margin-bottom: var(--space-2); }
/* Freshness stamp under the aside buttons (2026-07-28) — informational only. */
.aside-freshness { margin: var(--space-3) 0 0; text-align: center; font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; }
/* Lawyer's profile copy (P1 step 8, 2026-08-19): the page intro under the
   header, per-section intros at the top of a card body, the end-of-profile
   note, and a native disclosure for the longer live-session explanation.
   All visible text (his rule for long intros) — quiet, secondary voice. */
.profile-intro { margin: calc(-1 * var(--space-3)) 0 var(--space-5); max-width: 72ch; }
.profile-intro p { margin: 0 0 var(--space-2); font-size: 0.92rem; line-height: 1.55; color: var(--text-secondary); }
.profile-intro p.muted { color: var(--text-muted); font-size: 0.85rem; }
.section-intro { margin: 0 0 var(--space-5); max-width: 72ch; font-size: 0.88rem; line-height: 1.55; color: var(--text-secondary); }
.section-intro p { margin: 0 0 var(--space-2); }
.section-intro p:last-child { margin-bottom: 0; }
.profile-outro { margin: var(--space-4) 0 0; padding: 0 var(--space-2); max-width: 72ch; }
.profile-outro p { margin: 0 0 var(--space-2); font-size: 0.85rem; line-height: 1.55; }
.form-more { margin-top: var(--space-2); font-size: 0.82rem; line-height: 1.5; color: var(--text-muted); }
.form-more summary { cursor: pointer; color: var(--text-secondary); list-style: none; display: inline-flex; align-items: center; gap: var(--space-2); }
.form-more summary::-webkit-details-marker { display: none; }
.form-more summary::before { content: '+'; font-family: var(--font-mono); color: var(--text-muted); width: 1em; text-align: center; }
.form-more[open] summary::before { content: '\2013'; }
.form-more summary:hover { color: var(--primary); }
.form-more p { margin: var(--space-2) 0 0 calc(1em + var(--space-2)); }

/* 19. Settings page --------------------------------------------------------- */
.provider-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  margin-bottom: var(--space-3);
  transition: border-color 0.15s ease;
}
.provider-row:last-child { margin-bottom: 0; }
.provider-row:hover { border-color: var(--border-strong); }
.provider-row .provider-name { display: flex; align-items: center; gap: var(--space-3); font-weight: 600; }
.provider-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex-shrink: 0;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-default);
  color: var(--text-secondary);
}
.provider-ico svg { width: 20px; height: 20px; }
.provider-title { font-weight: 600; }
.provider-desc { font-weight: 400; font-size: 0.82rem; color: var(--text-muted); margin-top: 1px; }

/* Account identity header. */
.settings-identity { display: flex; align-items: center; gap: var(--space-4); }
.identity-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 56px; height: 56px; flex-shrink: 0;
  border-radius: 50%;
  background: var(--primary-bg); border: 1px solid var(--border-accent); color: var(--primary);
  font-family: var(--font-logo); font-weight: 700; font-size: 1.5rem;
}
.identity-email { font-size: 1.05rem; font-weight: 600; word-break: break-all; }
.identity-meta { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
/* Section kickers between the settings cards (design pass 2026-07-28) —
   extra air above each group so the seams read at a glance. */
.settings-sec { display: block; margin: var(--space-8) 0 var(--space-3); }

/* 20. Admin dashboard ------------------------------------------------------- */
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-4); margin-bottom: var(--space-6); }

/* 21. Users page ------------------------------------------------------------ */
.filters {
  display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: flex-end;
  margin-bottom: var(--space-5);
}
.filters .form-group { margin-bottom: 0; }
.filters .grow { flex: 1; min-width: 200px; }

/* 22. Tests page (card grid) ------------------------------------------------ */
.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: var(--space-4); }
.test-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-5);
  background: var(--glass), var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  transition: border-color 0.15s, box-shadow 0.2s, transform 0.1s;
}
.test-card:hover { border-color: var(--border-accent); box-shadow: var(--glow-cyan); transform: translateY(-2px); }
/* .test-stats buried 2026-08-06 (illustration round 2) — the signal window
   (§80) replaced the stats row + rate bar on the test card. */
.test-meta { display: flex; flex-direction: column; gap: var(--space-1); }
.test-meta .tm { display: inline-flex; align-items: center; gap: var(--space-2); font-size: 0.82rem; color: var(--text-muted); }
.test-meta .tm svg { width: 14px; height: 14px; flex-shrink: 0; opacity: 0.8; }
.test-meta .tm.is-overdue { color: var(--error); }
/* Running / stopped groups (expired + deactivated live below the fold). */
.test-group-head { display: flex; align-items: baseline; gap: var(--space-3); border-bottom: 1px solid var(--border-default); padding-bottom: var(--space-2); margin-bottom: var(--space-4); }
.test-group-head.is-stopped { margin-top: var(--space-8); }
.test-group-count { margin-left: auto; font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; }
.card-grid.is-stopped .test-card { opacity: 0.75; }
.card-grid.is-stopped .test-card:hover { opacity: 1; }
/* Action buttons never overflow the card — they wrap. */
.test-card-actions { flex-wrap: wrap; }
/* "Payouts ready" export line: money voice (green), appears only once every
   response is evaluated and payouts are still owed — its own quiet row, not a
   fourth button crowding the actions. */
.payout-ready {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: 0.82rem; font-family: var(--font-mono);
  color: var(--success); text-decoration: none;
}
.payout-ready:hover { text-decoration: underline; }
.payout-ready svg { width: 14px; height: 14px; flex-shrink: 0; }

/* 23. Form builder ---------------------------------------------------------- */
/* UX round 2026-08-08: each row is a CARD — a header toolbar (drag handle ·
   number · library chip · type · required · remove) over full-width fields.
   The old auto/1fr grid left the number and remove button floating in space
   once the options textarea made rows tall. */
.builder-questions { display: flex; flex-direction: column; gap: var(--space-3); }
.builder-row {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-3);
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  transition: border-color 0.15s ease;
}
.builder-row:hover { border-color: var(--border-strong); }
.builder-row-head { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.builder-row-head .row-spring { flex: 1; }
.builder-row-head select { width: auto; }
.builder-row .q-index {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  color: var(--primary); font-weight: 700; font-size: 0.8rem; font-family: var(--font-logo);
  background: var(--primary-bg); border: 1px solid var(--border-accent);
}
/* Remove = a quiet ghost that only turns dangerous on hover — a standing red
   box on every row diluted the danger voice (same call as the card ⋯ menu). */
.remove-q {
  background: transparent; border: 1px solid var(--border-default);
  color: var(--text-muted); border-radius: var(--radius-sm);
  width: 30px; height: 30px; flex-shrink: 0; cursor: pointer;
  font-size: 1rem; line-height: 1;
}
.remove-q:hover { background: var(--error-bg); border-color: rgba(251, 113, 133, 0.35); color: #fda4af; }
/* Drag (or focus the handle and use the arrow keys) to change the order
   testers see. */
.drag-handle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 30px; flex-shrink: 0;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--text-muted); cursor: grab;
}
.drag-handle:hover { color: var(--text-secondary); background: rgba(255, 255, 255, 0.04); }
/* Full-width field stack under the header. */
.builder-row .q-fields { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
.builder-row .q-desc-input { font-size: 0.85rem; color: var(--text-secondary); }
.drag-handle svg { width: 16px; height: 16px; }
.builder-row.is-dragging { opacity: 0.45; border-color: var(--border-accent); }
/* Section-heading rows read as structure, not questions. */
.builder-row.is-section input[name="questionText"] {
  font-family: var(--font-mono); font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-secondary); font-size: 0.82rem;
}
@media (max-width: 980px) { .dispatch-grid { grid-template-columns: 1fr; } }
/* Choice-question options (2026-08-08): the per-row textarea, one option per
   line — revealed by form-builder.js only on SingleChoice/MultiChoice rows.
   Locked tests show the list read-only (options are answer structure). */
.q-options-input {
  width: 100%; resize: vertical;
  font-size: 0.85rem; line-height: 1.5;
}
.q-options-locked { font-size: 0.82rem; }
/* Declutter round 2026-08-08: Required/Optional is a MODIFIER on the row, not
   a second peer dropdown — mono-uppercase, ghost dashed border, compact. The
   type select keeps the standard input look, so the two stop reading as twins. */
.builder-row .q-required {
  width: auto; padding: 8px 26px 8px 10px;
  font-family: var(--font-mono); font-size: 0.72rem;
  text-transform: uppercase; letter-spacing: 0.06em;
  background-color: transparent; border-style: dashed; color: var(--text-secondary);
}
.builder-row .q-required:focus-visible { border-style: solid; }

/* 24. Assignments page ------------------------------------------------------ */
.tester-pick { max-height: 460px; overflow-y: auto; display: flex; flex-direction: column; gap: var(--space-2); }
.tester-pick label {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  cursor: pointer;
}
.tester-pick label:hover { border-color: var(--border-strong); background: rgba(255, 255, 255, 0.02); }
.tester-pick .tester-sub { color: var(--text-muted); font-size: 0.8rem; }
.tester-pick label:has(input:checked) { border-color: var(--border-accent); background: var(--primary-bg); }
.tester-av {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; flex-shrink: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border-default);
  color: var(--text-secondary); font-family: var(--font-logo); font-weight: 700; font-size: 0.82rem;
}
.tester-info { display: flex; flex-direction: column; min-width: 0; }
.tester-email { font-size: 0.88rem; }

/* 25. Developer space ------------------------------------------------------- */
.response-feed { display: flex; flex-direction: column; gap: var(--space-4); }
.response-card {
  padding: var(--space-5);
  background: var(--glass), var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
}
.response-card .resp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4); }
.answer-block { padding: var(--space-3) 0; border-top: 1px solid var(--border-default); }
.answer-block .q { font-size: 0.82rem; color: var(--text-muted); margin-bottom: var(--space-1); }
.answer-block .a { color: var(--text-primary); }

/* Developer dashboard project cards. */
.project-card {
  display: flex; flex-direction: column; gap: var(--space-3);
  padding: var(--space-5);
  background: var(--glass), var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  transition: border-color 0.15s ease, box-shadow 0.2s ease, transform 0.1s ease;
}
.project-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow); transform: translateY(-2px); }
.project-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3); }
.project-name { margin: 0; font-size: 1.05rem; }
.project-due { font-size: 0.78rem; margin-top: -6px; }
/* Pin the stats → progress → actions footer to the card bottom so buttons
   line up across cards regardless of how much description/reward sits above. */
/* .project-stats + .project-progress buried 2026-08-06 (illustration round 2)
   — the signal window (§80) replaced them on both cards. Its margin-top:auto
   moved to .sig-win, which now bottom-aligns the card footers (the settled
   "buttons line up across a row" fix rides on it). */
.project-actions { display: flex; gap: var(--space-2); margin-top: var(--space-2); }
.project-actions .btn { flex: 1; }

/* Developer project (response feed). */
.resp-summary { display: flex; gap: var(--space-6); }
.resp-summary-stat { display: flex; flex-direction: column; align-items: flex-end; }
.resp-summary-stat .num { font-family: var(--font-logo); font-size: 1.8rem; font-weight: 700; line-height: 1; }
.resp-summary-stat .lbl { font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 4px; }

.insight-card { margin-bottom: var(--space-5); }
.rating-row { display: grid; grid-template-columns: 1.4fr 1fr auto; gap: var(--space-4); align-items: center; padding: var(--space-3) 0; border-top: 1px solid var(--border-default); }
.rating-row:first-child { border-top: none; padding-top: 0; }
.rating-row:last-child { padding-bottom: 0; }
.rating-q { font-size: 0.9rem; }
.rating-meter { display: flex; align-items: center; gap: var(--space-3); }
.rating-bar { flex: 1; height: 6px; border-radius: var(--radius-pill); background: rgba(255, 255, 255, 0.06); overflow: hidden; }
.rating-bar > span { display: block; height: 100%; border-radius: inherit; background: var(--primary-gradient); }
.rating-val { font-family: var(--font-logo); font-weight: 700; font-size: 0.95rem; white-space: nowrap; }
.rating-val .muted { font-weight: 400; font-size: 0.78rem; }
.rating-n { font-size: 0.78rem; white-space: nowrap; text-align: right; }
@media (max-width: 640px) { .rating-row { grid-template-columns: 1fr; gap: var(--space-2); } .rating-n { text-align: left; } }

.resp-who { display: flex; align-items: center; gap: var(--space-3); }
.resp-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex-shrink: 0; border-radius: 50%;
  background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-default);
  color: var(--text-secondary); font-family: var(--font-logo); font-weight: 700;
}
.resp-date { font-size: 0.78rem; }
.resp-answers .answer-block:first-child { border-top: none; padding-top: 0; }
.answer-block .q-i {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.05); color: var(--text-secondary);
  font-size: 0.74rem; font-weight: 700; font-family: var(--font-logo); margin-right: 4px;
}

/* 26. Feedback form (star rating) ------------------------------------------- */
.star-rating { display: inline-flex; flex-direction: row-reverse; gap: var(--space-1); position: relative; }
/* Visually hidden but FOCUSABLE (2026-08-07, review round 3): display:none
   removed the radios from the tab order and the accessibility tree, making a
   rating question mouse-only — a keyboard/screen-reader user could not answer
   it at all (and required ratings made the form unsubmittable). The sibling
   :checked ~ label styling below still works — the inputs keep their DOM
   position, they just paint nothing. */
.star-rating input {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  clip-path: inset(50%); overflow: hidden; opacity: 0; pointer-events: none;
}
.star-rating input:focus-visible + label {
  outline: 2px solid var(--primary); outline-offset: 2px; border-radius: var(--radius-sm);
}
/* Unselected stars: a visible "empty" grey (was --bg-elevated ≈ the card
   colour, so they read as nearly invisible until hovered/picked). */
.star-rating label { font-size: 1.8rem; color: var(--text-muted); cursor: pointer; line-height: 1; transition: color 0.1s; }
.star-rating label:hover,
.star-rating label:hover ~ label,
.star-rating input:checked ~ label { color: var(--primary); }
.star-display { color: var(--primary); letter-spacing: 2px; }

/* Feedback form: numbered question blocks + sticky submit bar. */
.feedback-form .q-list { display: flex; flex-direction: column; }
.q-block { padding: var(--space-5) 0; border-top: 1px solid var(--border-default); }
.q-block:first-child { padding-top: 0; border-top: none; }
.q-block:last-child { padding-bottom: 0; }
.q-head { display: flex; align-items: flex-start; gap: var(--space-3); margin-bottom: var(--space-4); }
.q-num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; flex-shrink: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-default);
  color: var(--text-secondary); font-family: var(--font-logo); font-weight: 700; font-size: 0.85rem;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.q-block.is-answered .q-num {
  background: var(--primary-bg); border-color: var(--border-accent); color: var(--primary);
  font-size: 0; /* hide number, show check below */
}
.q-block.is-answered .q-num::after { content: '✓'; font-size: 0.9rem; }
.q-head-text { padding-top: 3px; flex: 1; min-width: 0; }
.q-text { display: block; font-weight: 600; font-size: 1rem; color: var(--text-primary); }
.q-type {
  display: inline-block; margin-top: 3px;
  font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted);
}
/* Optional admin-authored helper text under a question, aligned with the input. */
.q-desc { margin: calc(-1 * var(--space-3)) 0 var(--space-4); padding-left: calc(28px + var(--space-3)); font-size: 0.88rem; color: var(--text-secondary); line-height: 1.5; }
.q-input { padding-left: calc(28px + var(--space-3)); }
.star-rating label:hover { transform: scale(1.12); }
/* (The checked-star text-shadow glow was removed 2026-09-02 — no glows.) */

/* Styled file dropzone (replaces the bare <input type=file>). */
.file-drop {
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
  padding: var(--space-5);
  border: 1.5px dashed var(--border-strong); border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02); color: var(--text-secondary); cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.file-drop:hover { border-color: var(--border-accent); background: var(--primary-bg); }
/* Active drag target (JS adds .is-dragover on dragenter/over). */
.file-drop.is-dragover { border-color: var(--primary); border-style: solid; background: var(--primary-bg); }
.file-drop.is-dragover svg { color: var(--primary); }
.file-drop svg { width: 26px; height: 26px; color: var(--text-muted); }
.file-drop-text { font-size: 0.9rem; }
.file-drop-hint { font-size: 0.78rem; color: var(--text-muted); }
.file-drop input[type="file"] { display: none; }

.form-sticky-bar {
  position: sticky; bottom: var(--space-4); z-index: 5;
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-4);
  margin-top: var(--space-5); padding: var(--space-3) var(--space-4);
  background: var(--bg-elevated); border: 1px solid var(--border-default);
  border-radius: var(--radius); box-shadow: var(--shadow);
}
.form-sticky-bar .progress-text { font-size: 0.85rem; color: var(--text-secondary); }
.form-sticky-bar .progress-text [data-answered] { color: var(--primary); font-weight: 700; }
.form-sticky-actions { display: flex; gap: var(--space-3); }
@media (max-width: 560px) {
  .q-input { padding-left: 0; }
  .form-sticky-bar { flex-direction: column; align-items: stretch; gap: var(--space-3); }
  .form-sticky-actions .btn { flex: 1; }
}

/* Selected-file preview cards (JS-rendered from the file input). */
.file-field { display: flex; flex-direction: column; gap: var(--space-3); }
.file-previews { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.file-card {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--bg-surface); border: 1px solid var(--border-default); border-radius: var(--radius-sm);
}
.file-thumb {
  flex: none; width: 40px; height: 40px; border-radius: var(--radius-sm); overflow: hidden;
  display: grid; place-items: center; background: var(--bg-elevated); color: var(--text-muted);
}
.file-thumb img { width: 100%; height: 100%; object-fit: cover; }
.file-thumb.is-video svg { width: 20px; height: 20px; }
.file-meta { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.file-name { font-size: 0.85rem; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-size { font-size: 0.78rem; color: var(--text-muted); }
.file-remove {
  flex: none; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  background: none; border: none; border-radius: var(--radius-sm); color: var(--text-muted); cursor: pointer;
  touch-action: manipulation; transition: color 0.15s ease, background 0.15s ease;
}
.file-remove svg { width: 16px; height: 16px; }
.file-remove:hover { color: var(--error); background: rgba(232, 112, 112, 0.10); }

/* Upload progress (shown in the sticky bar during an XHR submit). */
.upload-progress { display: flex; align-items: center; gap: var(--space-3); flex: 1; min-width: 0; }
.upload-note { flex: none; font-size: 0.78rem; color: var(--primary); white-space: nowrap; }
.upload-track { flex: 1; height: 6px; background: var(--bg-elevated); border-radius: var(--radius-pill); overflow: hidden; }
.upload-fill { display: block; height: 100%; width: 0; background: var(--primary); transition: width 0.2s ease; }
@media (prefers-reduced-motion: reduce) { .upload-fill { transition: none; } }

/* Choice questions (2026-08-08): SingleChoice radios / MultiChoice checkboxes.
   Native inputs stay visible + focusable (the a11y lesson from the star
   rating); .choice-mark is only a themed halo behind them. Rows are full-width
   touch targets. Long SingleChoice lists render as a native <select>
   (.choice-select) — same data, same name. */
.choice-group { display: flex; flex-direction: column; gap: var(--space-2); max-width: 520px; }
.choice-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) var(--space-3); cursor: pointer;
  border: 1px solid var(--border-default); border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02);
  transition: border-color 0.1s;
}
.choice-row:hover { border-color: var(--border-strong); }
.choice-row input { accent-color: var(--primary); width: 16px; height: 16px; flex: none; cursor: pointer; }
.choice-row input:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.choice-row:has(input:checked) { border-color: var(--border-accent); background: var(--primary-bg); }
.choice-label { color: var(--text-primary); font-size: 0.92rem; }
.choice-mark { display: none; } /* reserved hook — the native input carries the visual for now */
.choice-select { max-width: 420px; }

/* 27. Error pages ----------------------------------------------------------- */
.error-screen { position: relative; overflow: hidden; min-height: 100vh; display: grid; place-items: center; padding: var(--space-6); text-align: center; }
.error-card { position: relative; z-index: 1; max-width: 460px; }
.error-code { font-family: var(--font-logo); font-size: 5.5rem; font-weight: 800; line-height: 1; background: var(--primary-gradient); -webkit-background-clip: text; background-clip: text; color: transparent; }
.error-screen p { color: var(--text-secondary); }

/* The ambient radial glow behind the card was retired 2026-09-02 (Scoreboard:
   no glows). The partial still emits the element; it simply paints nothing. */
.error-aura { display: none; }

/* Themed icon badge. */
.error-badge {
  display: inline-grid; place-items: center;
  width: 72px; height: 72px; margin: 0 auto var(--space-4);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-default);
  color: var(--text-secondary);
}
.error-badge svg { width: 32px; height: 32px; }
.error-badge.tone-accent { background: var(--primary-bg); border-color: var(--border-accent); color: var(--primary); }
.error-badge.tone-warning { background: var(--warning-bg); border-color: rgba(245, 196, 81, 0.3); color: var(--warning); }
.error-badge.tone-danger { background: var(--error-bg); border-color: rgba(251, 113, 133, 0.3); color: var(--error); }

/* Solid code colour for non-accent tones (default stays the cyan gradient). */
.error-code.tone-warning { background: none; color: var(--warning); -webkit-text-fill-color: var(--warning); }
.error-code.tone-danger { background: none; color: var(--error); -webkit-text-fill-color: var(--error); }

.error-actions { display: flex; gap: var(--space-3); justify-content: center; flex-wrap: wrap; margin-top: var(--space-5); }

/* 28. File attachments ------------------------------------------------------ */
.file-grid { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-2); }
.file-thumb img { width: 132px; height: 86px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--border-default); transition: border-color 0.15s; }
.file-thumb:hover img { border-color: var(--border-accent); }
.file-link {
  display: inline-flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-default);
  border-radius: var(--radius);
  font-size: 0.85rem;
}
.file-link:hover { border-color: var(--border-accent); text-decoration: none; }
.file-link svg { width: 15px; height: 15px; flex-shrink: 0; opacity: 0.8; }

/* Async staged-upload card states: each picked file uploads immediately with
   its own mini progress bar; done/failed read from the size line + tone. */
.file-card.is-uploading .file-thumb { opacity: 0.55; }
.file-card.is-error { border-color: rgba(232, 112, 112, 0.45); }
.file-card.is-error .file-size { color: var(--error); }
.file-prog {
  display: block; height: 3px; margin-top: var(--space-1);
  background: rgba(255, 255, 255, 0.08); border-radius: var(--radius-pill);
  overflow: hidden;
}
.file-prog-fill {
  display: block; height: 100%; width: 0;
  background: var(--primary);
  transition: width 0.2s ease;
}

/* 29. Utilities & responsive ------------------------------------------------ */
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: var(--space-2); }
.gap-3 { gap: var(--space-3); }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mb-0 { margin-bottom: 0; }
.w-full { width: 100%; }
.empty-state { text-align: center; padding: var(--space-8) var(--space-5); color: var(--text-muted); }
.empty-state .icon { font-size: 2.6rem; margin-bottom: var(--space-3); opacity: 0.6; }
.empty-state .icon svg { width: 46px; height: 46px; color: var(--text-muted); }
.page-header { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--space-4); margin-bottom: var(--space-6); }
.page-header h1 { margin-bottom: var(--space-1); }

.sidebar-backdrop { display: none; }

@media (max-width: 860px) {
  .sidebar {
    position: fixed; left: 0; top: 0; z-index: 50;
    transform: translateX(-100%);
    /* visibility flips AFTER the slide-out (2026-08-07, review round 3):
       transform alone left every off-screen nav link in the Tab order, so
       keyboard users walked an invisible menu. Hidden = out of the tab
       order and the accessibility tree; the 0.2s delay lets the close
       animation finish, and opening flips it back instantly. */
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0s 0.2s;
  }
  .app-shell.sidebar-open .sidebar {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.2s ease, visibility 0s 0s;
  }
  .app-shell.sidebar-open .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 40;
    background: rgba(0, 0, 0, 0.55);
  }
  .mobile-nav-toggle { display: block; }
  .content { padding: var(--space-5) var(--space-4); }
  .profile-grid, .dispatch-grid { grid-template-columns: 1fr; }
}

/* 30. Admin response evaluation --------------------------------------------- */
.detail-sub { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-2); }
.eval-aside { position: sticky; top: var(--space-5); }
.eval-payout { border-top: 1px solid var(--border-default); margin-top: var(--space-4); padding-top: var(--space-4); }
@media (max-width: 900px) { .eval-aside { position: static; } }

/* 31. Accessibility (keyboard focus + reduced motion + touch) --------------- */
/* Visible focus ring for keyboard users on every interactive element. Uses
   :focus-visible so it never shows on plain mouse clicks. */
a:focus-visible, button:focus-visible, .btn:focus-visible, .nav-item:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible,
summary:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
/* Custom controls whose real <input> is visually hidden. */
.chip:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }
.switch:has(input:focus-visible) .slider { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Remove the 300ms tap delay; give tiny controls a comfortable touch target. */
a, button, .btn, label.chip, .switch, .nav-item, .pw-toggle { touch-action: manipulation; }
.alert-close { width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; }

/* Honour the OS "reduce motion" setting across the whole app (login.css has its
   own block; this covers every other page). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* 44px touch targets (2026-08-20 tester report — the project's own rule).
   Icon buttons grow always (four pixels cost nothing); row-like controls
   (list options, tabs, social glyphs) grow only for coarse pointers so the
   dense desktop layouts keep their rhythm. Later rules win over the 32px
   .alert-close above — additive-section discipline. */
.alert-close { width: 44px; height: 44px; }
.topnav-gear { width: 44px; height: 44px; }
@media (pointer: coarse) {
  .combo-list li { min-height: 44px; display: flex; align-items: center; }
  .lb-tab { min-height: 44px; }
  .log-group > summary, .topnav-menu > summary, .decline-menu > summary { min-height: 44px; }
  .site-footer-social a { width: 44px; height: 44px; align-items: center; justify-content: center; }
}

/* Skip link (2026-08-20): the first tab stop on the three role layouts —
   off-screen until focused, then a cyan chip at the top left. */
.skip-link {
  position: absolute; left: var(--space-3); top: var(--space-3); z-index: 100;
  padding: var(--space-2) var(--space-4);
  background: var(--primary); color: var(--primary-ink);
  font-family: var(--font-mono); font-size: 0.8rem; font-weight: 700;
  border-radius: var(--radius-sm);
  transform: translateY(-300%);
}
.skip-link:focus { transform: none; }

/* 32. Changelog — horizontal timeline (newest left → oldest right) ---------- */
.cl-timeline { position: relative; }
.cl-timeline-bar { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4); }
.cl-timeline-hint {
  font-family: var(--font-logo); font-size: 0.78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--primary);
  padding: 3px 12px; border: 1px solid var(--border-accent); background: var(--primary-bg);
  border-radius: var(--radius-pill);
}
.cl-nav { display: flex; gap: var(--space-2); }
.cl-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--radius);
  border: 1px solid var(--border-default); background: var(--bg-elevated); color: var(--text-secondary);
  cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s, opacity 0.15s;
}
.cl-arrow:hover { border-color: var(--border-accent); color: var(--primary); background: var(--primary-bg); }
.cl-arrow:disabled { opacity: 0.32; cursor: default; pointer-events: none; }

/* Scroll rail. A right-edge mask fades the deeper past; the spine line runs
   under the day nodes. Columns align to the top so the spine stays level even
   when a card is expanded (detail grows downward). */
.cl-track {
  display: flex; align-items: flex-start; gap: var(--space-4);
  overflow-x: auto; overflow-y: hidden;
  padding: 0 0 var(--space-4);
  scroll-snap-type: x proximity; scroll-behavior: smooth;
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 96px), transparent 100%);
          mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 96px), transparent 100%);
}
.cl-track::-webkit-scrollbar { height: 8px; }
.cl-track::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--radius-pill); }
.cl-track::-webkit-scrollbar-track { background: transparent; }

/* One column per day; the day's changes stack vertically inside it. */
.cl-col {
  position: relative; flex: 0 0 300px; max-width: 300px;
  scroll-snap-align: start;
  display: flex; flex-direction: column;
}
/* Spine segment — sits at the node's centre and bridges the gap to the next
   column. Offset = date row (18px) + its margin (space-2) + half the 16px
   node box (8px) = the dot's vertical centre. */
.cl-col::before {
  content: ''; position: absolute; z-index: 0;
  top: calc(17px + var(--space-2) + 8px); left: 0; width: calc(100% + var(--space-4)); height: 2px;
  background: var(--border-default);
}
.cl-col:last-child::before { width: 100%; -webkit-mask-image: linear-gradient(to right, #000, transparent); mask-image: linear-gradient(to right, #000, transparent); }

.cl-col-date {
  display: flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-logo); font-size: 0.8rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted);
  margin-bottom: var(--space-2);
}
.cl-col-count {
  font-family: var(--font-body); font-size: 0.7rem; font-weight: 700; letter-spacing: 0;
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: var(--radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-secondary); background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border-default);
}
.cl-node {
  position: relative; z-index: 1; width: 12px; height: 12px; margin-bottom: var(--space-4);
  border-radius: 50%; background: var(--primary-gradient); box-shadow: var(--glow-cyan);
  border: 2px solid var(--bg-surface);
}

/* Vertical stack of a day's changes. */
.cl-stack { display: flex; flex-direction: column; gap: var(--space-3); }
/* Cap the column: overflow changes hide until "Show more" expands the stack. */
.cl-stack:not(.is-expanded) .cl-overflow { display: none; }
.cl-more {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  align-self: flex-start; margin-top: 2px;
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 600;
  padding: 7px 14px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-default); background: var(--bg-elevated); color: var(--text-secondary);
  cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.cl-more:hover { border-color: var(--border-accent); color: var(--primary); background: var(--primary-bg); }
.cl-stack:not(.is-expanded) .cl-more-less { display: none; }
.cl-stack.is-expanded .cl-more-show { display: none; }
.cl-card {
  position: relative;
  display: flex; flex-direction: column;
  padding: var(--space-4);
  border: 1px solid var(--border-default); border-radius: var(--radius-lg);
  background: var(--bg-card);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.cl-card:hover { border-color: var(--border-strong); }
.cl-card.is-open { border-color: var(--border-accent); box-shadow: 0 12px 30px -14px rgba(0, 0, 0, 0.6); }

/* Head — the always-visible row; a button when there's detail to reveal. */
.cl-card-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3);
  width: 100%; text-align: left; background: none; border: none; padding: 0; margin: 0;
  color: inherit; font: inherit; cursor: pointer;
}
.cl-card.is-static .cl-card-head { cursor: default; }
.cl-card-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); min-width: 0; }
.cl-card-subject { font-weight: 600; color: var(--text-primary); line-height: 1.35; }
.cl-chevron { flex-shrink: 0; color: var(--text-muted); margin-top: 2px; transition: transform 0.2s, color 0.15s; }
.cl-card-head:hover .cl-chevron { color: var(--primary); }
.cl-card.is-open .cl-chevron { transform: rotate(180deg); color: var(--primary); }

.cl-badge {
  display: inline-block; vertical-align: 1px;
  font-family: var(--font-body); font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  padding: 2px 8px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-default); background: rgba(255, 255, 255, 0.04); color: var(--text-secondary);
}
.cl-badge.tone-feature { background: var(--primary-bg); border-color: var(--border-accent); color: var(--primary); }
.cl-badge.tone-fix { background: var(--error-bg); border-color: rgba(251, 113, 133, 0.32); color: #fda4af; }
.cl-badge.tone-perf, .cl-badge.tone-improve { background: var(--violet-bg); border-color: rgba(139, 92, 246, 0.35); color: #c4b5fd; }
.cl-badge.tone-security { background: var(--magenta-bg); border-color: rgba(240, 67, 140, 0.35); color: #f9a8d4; }
.cl-badge.tone-release { background: var(--success-bg); border-color: rgba(52, 211, 153, 0.32); color: #86efac; }

/* Detail — hidden until the card is expanded. */
.cl-card-body {
  display: none; margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--border-default);
  color: var(--text-secondary); font-size: 0.88rem; line-height: 1.55;
}
.cl-card.is-open .cl-card-body { display: block; animation: clFade 0.2s ease; }
.cl-card-body div { margin-bottom: 3px; }
@keyframes clFade { from { opacity: 0; transform: translateY(-3px); } to { opacity: 1; transform: none; } }

.cl-card-meta { margin-top: var(--space-3); font-size: 0.76rem; color: var(--text-muted); }
.cl-hash {
  font-family: ui-monospace, 'SFMono-Regular', Menlo, monospace; font-size: 0.78rem;
  background: rgba(255, 255, 255, 0.04); border: 1px solid var(--border-default);
  border-radius: var(--radius-sm); padding: 1px 6px; color: var(--text-secondary);
}

/* 33. Test brief (description + reward range) ------------------------------- */
.test-brief { margin-bottom: var(--space-5); }
.test-brief .card-body { display: flex; flex-direction: column; gap: var(--space-3); }
.reward-line { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.reward-pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-logo); font-weight: 700; font-size: 1.02rem;
  padding: 5px 14px; border-radius: var(--radius-pill);
  color: #05221a; background: var(--success);
  box-shadow: 0 6px 18px -8px rgba(52, 211, 153, 0.5);
}
.reward-pill svg { width: 15px; height: 15px; }
.reward-note { font-size: 0.82rem; color: var(--text-muted); }
.test-desc { margin: 0; color: var(--text-secondary); white-space: pre-line; }
/* Compact reward chip for cards/tables/rows. */
.reward-chip {
  display: inline-flex; align-items: center; gap: 5px;
  font-family: var(--font-logo); font-weight: 700; font-size: 0.82rem;
  padding: 2px 10px; border-radius: var(--radius-pill);
  color: #86efac; background: var(--success-bg); border: 1px solid rgba(52, 211, 153, 0.3);
}
.reward-chip svg { width: 12px; height: 12px; }
.test-desc-snip {
  margin: 0; color: var(--text-muted); font-size: 0.85rem;
  display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* 34. Test-detail popup ----------------------------------------------------- */
/* The whole assignment row opens the popup (cursor hints it; the action
   buttons keep their own behaviour). */
.assignment-row[data-detail] { cursor: pointer; }

.modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  display: flex; align-items: center; justify-content: center; padding: var(--space-5);
  background: rgba(4, 7, 15, 0.72);
  animation: modal-fade 0.16s ease both;
}
.modal-overlay[hidden] { display: none; }
.modal {
  width: 100%; max-width: 460px; max-height: 85vh; overflow-y: auto;
  background: var(--glass), var(--bg-card);
  border: 1px solid var(--border-strong); border-radius: var(--radius-lg); box-shadow: var(--shadow);
  animation: modal-pop 0.18s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding: var(--space-5) var(--space-5) var(--space-3); }
.modal-head h3 { font-size: 1.2rem; }
.modal-close {
  background: none; border: none; color: var(--text-muted); font-size: 1.5rem; line-height: 1;
  cursor: pointer; width: 32px; height: 32px; border-radius: var(--radius-sm); flex-shrink: 0;
}
.modal-close:hover { color: var(--text-primary); background: rgba(255, 255, 255, 0.05); }
.modal-body { padding: 0 var(--space-5) var(--space-5); display: flex; flex-direction: column; gap: var(--space-4); }
.modal-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.modal-tag { font-size: 0.82rem; color: var(--text-muted); }
.modal-actions {
  display: flex; flex-wrap: wrap; gap: var(--space-2); justify-content: flex-end;
  padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border-default);
}
@keyframes modal-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes modal-pop { from { opacity: 0; transform: translateY(10px) scale(0.98); } to { opacity: 1; transform: none; } }
/* Card titles that open the shared test-detail popup (admin tests + developer
   projects): read as text at rest, cyan (= act) on hover. */
.card-brief-link { color: inherit; text-decoration: none; }
.card-brief-link:hover, .card-brief-link:focus-visible { color: var(--primary); text-decoration: none; }

/* 35. Scoreboard voice utilities --------------------------------------------- */
/* The three text voices (see CLAUDE.md design law): sentences use the body
   font; SECTION LABELS use .kicker (uppercase mono); data uses .mono.
   Use these classes — don't hand-style labels, or the voice drifts. */
.kicker {
  font-family: var(--font-mono);
  font-size: 0.78rem; font-weight: 600;
  /* Wide tracking is the live voice's signature — deliberately OFF the quiet
     label ramp (calibration 2026-08-25 kept it): the one kicker per screen
     must not read like the whisper labels wearing magenta. */
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--magenta);            /* magenta = the LIVE voice */
}
.kicker-muted { color: var(--text-muted); }  /* structural label, not live */
.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.money { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--success); }

/* 36. Motion primitives ------------------------------------------------------ */
/* Law: motion has meaning, ONE orchestrated moment per screen. Count-up =
   fresh data, reveal = content arriving, float + glow = the robot idling /
   loading. Nothing loops except the terminal cursor and the idle robot.
   JS (main.js) arms these only when prefers-reduced-motion is off; content
   is always visible without JS. */
.cursor-blink { color: var(--primary); font-weight: 700; animation: pl-blink 1.1s steps(1) infinite; }
@keyframes pl-blink { 50% { opacity: 0; } }
@keyframes pl-float { 50% { transform: translateY(-7px); } }
/* Logo glow scale — the PNG is clean; glow is ALWAYS applied here, per context. */
.glow-0 { filter: none; }
.glow-1 { filter: drop-shadow(0 0 8px rgba(46, 230, 255, 0.35)); }
.glow-2 { filter: drop-shadow(0 0 14px rgba(46, 230, 255, 0.45)) drop-shadow(0 0 36px rgba(46, 230, 255, 0.22)); }
/* Staggered entrance — JS adds .is-armed (hide) then .in (rise). */
.reveal.is-armed { opacity: 0; transform: translateY(6px); }
.reveal.in { animation: pl-rise 0.4s ease forwards; }
@keyframes pl-rise { to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .cursor-blink { animation: none; }
  .reveal.is-armed { opacity: 1; transform: none; }
}

/* 37. Tester top navigation + mission control -------------------------------- */
/* Testers get a top bar instead of the sidebar (3 links don't justify 256px);
   the dashboard is mission control: hero answers "what should I do now?",
   the scoreboard shows career numbers, the log is dense history. */
.topnav-shell { min-height: 100vh; display: flex; flex-direction: column; }
/* 1560 (owner round 2026-08-14, admin feedback vs. a competitor reference;
   was 1400 since 2026-07-26, 1080 before that): big monitors still left the
   shell swimming in dead margin. The rail/aside caps below are fixed px, so
   every widening pays the MISSION LOG and hero, not the side rails.
   Prose/form pages keep their own inner measures — the frame widens,
   reading lines don't. */
.topnav-shell > .content { padding: var(--space-6); max-width: 1560px; width: 100%; margin: 0 auto; }
.topnav {
  display: flex; align-items: center; gap: var(--space-6);
  height: 64px; padding: 0 var(--space-6);
  border-bottom: 1px solid var(--border-default);
  background: rgba(10, 11, 13, 0.82);
  backdrop-filter: blur(12px);
  position: sticky; top: 0; z-index: 20;
}
.topnav-brand { display: inline-flex; align-items: center; gap: var(--space-2); text-decoration: none; }
.topnav-brand:hover { text-decoration: none; }
.topnav-mark { height: 26px; width: auto; }
.topnav-wordmark { height: 12px; width: auto; opacity: 0.95; }
.topnav-nav { display: flex; gap: var(--space-2); height: 100%; }
.topnav-link {
  display: inline-flex; align-items: center; height: 100%; padding: 0 var(--space-3);
  font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-secondary); border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.topnav-link:hover { color: var(--text-primary); text-decoration: none; }
.topnav-link.active { color: var(--text-primary); border-bottom-color: var(--primary); }
.topnav-right { margin-left: auto; display: flex; align-items: center; gap: var(--space-4); }
/* Profile-completion nudge — deliberately quiet: a hairline progress bar +
   muted percent. The full pitch lives on the profile page, not the navbar. */
.topnav-nudge { display: inline-flex; align-items: center; gap: var(--space-2); text-decoration: none; }
.topnav-nudge:hover { text-decoration: none; }
.nudge-track { width: 44px; height: 4px; border-radius: var(--radius-pill); background: var(--bg-elevated); border: 1px solid var(--border-default); overflow: hidden; }
.nudge-fill { display: block; height: 100%; background: var(--primary); opacity: 0.7; }
.topnav-nudge:hover .nudge-fill { opacity: 1; }
.nudge-pct { font-family: var(--font-mono); font-size: 0.74rem; color: var(--text-muted); }
.topnav-nudge:hover .nudge-pct { color: var(--text-secondary); }
/* The signed-in user — visible, not a whisper. */
.topnav-user { display: inline-flex; align-items: center; gap: var(--space-2); }
.topnav-av {
  width: 30px; height: 30px; border-radius: var(--radius-sm); flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--primary-bg); border: 1px solid var(--border-accent);
  color: var(--primary); font-family: var(--font-logo); font-weight: 700; font-size: 0.85rem;
}
.topnav-name { font-size: 0.9rem; font-weight: 600; color: var(--text-primary); max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.topnav-out {
  background: none; border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  color: var(--text-muted); font-family: var(--font-body); font-size: 0.78rem; font-weight: 600;
  padding: 5px 12px; cursor: pointer; transition: color 0.15s, border-color 0.15s;
}
.topnav-out:hover { color: var(--text-primary); border-color: var(--border-strong); }

/* Hero + scoreboard side by side. */
/* Hero band (designer round 2026-07-26; was a 1.35fr/1fr grid sharing the top
   row with a 4-tile scoreboard): the hero is now the FULL-WIDTH first thing on
   the page and the only element wearing the accent frame — first paint answers
   exactly one question. The stats live in .score-strip below (§71). */
.mission-top { margin-bottom: var(--space-4); }
.mission-hero {
  border: 1px solid var(--border-accent);
  background: var(--bg-surface);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
}
.mission-hero .kicker { display: block; margin-bottom: var(--space-2); }
.mission-title { font-size: 2.3rem; margin: 0 0 var(--space-2); letter-spacing: -0.015em; }
/* The one loud control on the page. */
.mission-actions .btn { font-size: 1rem; padding: 12px 26px; }
.mission-meta { color: var(--text-secondary); margin: 0 0 var(--space-5); font-size: 0.92rem; }
.mission-meta a { font-weight: 600; }
/* The hero's facts row (owner call 2026-09-10): reward, countdowns, question
   count and the recording ask as ONE row of mono chips at one height, spaced
   by gap — the body-font money and the "read the brief" link used to sit at
   a different height in the same line; the brief is a button below now. */
.mission-facts { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-4); margin: 0 0 var(--space-5); }
.mission-facts > * { display: inline-flex; align-items: center; min-height: 22px; font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); }
.mission-facts > .money { color: var(--success); }
.mission-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
/* Empty state keeps the band but not the accent — nothing here to act on.
   (.mission-empty-mark retired 2026-08-07, hero-actor round: the robot moved
   into the §81 scene svg; the text block is the one flow child now.) */
.mission-hero.is-empty { display: flex; align-items: center; gap: var(--space-6); border-color: var(--border-default); }
/* "Join the Discord" line in the empty state — quieter than the main meta. */
.mission-community { margin: var(--space-4) 0 0; font-size: 0.85rem; color: var(--text-muted); }


/* Mission log — dense scannable rows. */
.mission-log-head { display: flex; align-items: baseline; gap: var(--space-3); border-bottom: 1px solid var(--border-default); padding-bottom: var(--space-2); }
.mission-log-count { margin-left: auto; font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; }
.mission-log-empty { color: var(--text-muted); font-size: 0.9rem; padding: var(--space-5) 0; }
.mission-row {
  /* EVERY trailing column is FIXED width so the reward / date / state columns
     line up vertically across rows. Each .mission-row is its OWN grid, so a
     content-sized (max-content/auto) trailing column resolves to a different
     width per row → the flexible name column absorbs a different amount → the
     columns go ragged (the bug this replaces). The name is the only flexible
     track (minmax(0,1fr)); it truncates with an ellipsis instead of pushing. */
  display: grid; grid-template-columns: 14px minmax(0, 1fr) 96px 56px 168px;
  gap: var(--space-4); align-items: center;
  padding: var(--space-3) var(--space-1);
  border-bottom: 1px solid var(--border-default);
  position: relative; cursor: pointer;
  transition: background 0.15s;
}
.mission-row:hover { background: rgba(46, 230, 255, 0.03); }
.mission-row::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: var(--primary); transform: scaleY(0); transition: transform 0.15s ease; }
.mission-row:hover::before { transform: scaleY(1); }
.mission-row.is-muted .mission-name, .mission-row.is-muted .mission-sub { color: var(--text-muted); }
.mission-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--text-muted); }
.mission-dot.is-good { background: var(--success); }
.mission-dot.is-warn { background: var(--warning); }
.mission-dot.is-bad { background: var(--error); }
.mission-what { min-width: 0; }
.mission-name { display: block; font-weight: 600; font-size: 0.92rem; color: var(--text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mission-sub { display: block; font-size: 0.78rem; color: var(--text-muted); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mission-reward { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mission-reward .money { font-size: 0.82rem; }
.mission-none { color: var(--text-muted); }
.mission-date { font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; white-space: nowrap; }
.mission-state { display: flex; justify-content: flex-end; align-items: center; gap: var(--space-2); }
.mission-word { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); }
.mission-word.is-paid { color: var(--success); } /* paid = the money voice */
.mission-link { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; }

/* Grouped mission log — sections (Action needed / In progress / Submitted / …) */
.mission-group + .mission-group { margin-top: var(--space-5); }
.mission-group-head { display: flex; align-items: baseline; gap: var(--space-2); padding: 0 var(--space-1) var(--space-2); }
.mission-group-title { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); }
.mission-group-count { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); }
/* "Action needed" is the one thing that wants the tester — the live voice. */
.mission-group.is-action .mission-group-title { color: var(--magenta); }
.mission-group.is-action .mission-group-count { color: var(--magenta); opacity: 0.75; }

/* Mission-log pager (client-side; JS-only, hidden without it). */
.mission-pager { display: flex; align-items: center; justify-content: center; gap: var(--space-4); margin-top: var(--space-5); }
.pager-status { font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; min-width: 88px; text-align: center; }

@media (max-width: 720px) {
  .mission-hero { padding: var(--space-5); }
  .mission-title { font-size: 1.7rem; }
  .topnav { flex-wrap: wrap; height: auto; padding: var(--space-2) var(--space-4); row-gap: 0; }
  .topnav-nav { order: 3; width: 100%; justify-content: flex-start; height: 42px; }
  .topnav-name { display: none; } /* keep the avatar, drop the name */
  .topnav-nudge { display: none; }
  /* Owner report 2026-08-04: brand + icons overflowed the first row on
     phones, wrapping into a THREE-row header (brand / icons / nav). Tighter
     gaps + no terminal cursor keep them on one shared row down to 360px,
     and the nav row scrolls instead of clipping GUIDES. */
  .topnav { column-gap: var(--space-3); }
  .topnav-right { gap: var(--space-2); }
  .topnav-brand .cursor-blink { display: none; }
  .topnav-link { padding: 0 var(--space-2); }
  .topnav-nav { overflow-x: auto; }
  .topnav-shell > .content { padding: var(--space-4); }
  .mission-row { grid-template-columns: 14px minmax(0, 1fr) auto; }
  .mission-reward, .mission-date { display: none; }
}

/* Hero description teaser — a 2-line clamp under the title; the full brief opens
   in the detail modal ("read the brief"). Fills the hero without re-cluttering. */
.mission-preview {
  color: var(--text-secondary); font-size: 0.92rem; line-height: 1.5;
  margin: 0 0 var(--space-3); max-width: 60ch;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* Topnav account menu — the username chip is a native details-dropdown holding
   Settings + Sign out (no-JS-safe; main.js closes on outside click / Escape). */
.topnav-menu { position: relative; }
/* display:flex (block-level), not inline-flex: as an inline-level box inside
   the block <details> it sat on a baseline and the details grew a strut below
   it, floating the whole chip ~3px above the bar's centre (owner report
   2026-08-16, visible once the robot avatar made the chip 38px tall). */
.topnav-menu > summary { display: flex; list-style: none; cursor: pointer; border-radius: var(--radius-sm); padding: 3px 6px; }
.topnav-menu > summary::-webkit-details-marker { display: none; }
.topnav-menu > summary:hover { background: rgba(255, 255, 255, 0.04); }
.topnav-menu > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.topnav-caret { width: 12px; height: 12px; color: var(--text-muted); flex-shrink: 0; transition: transform 0.15s; }
.topnav-menu[open] .topnav-caret { transform: rotate(180deg); }
.topnav-menu-panel {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 60;
  min-width: 180px; padding: var(--space-2);
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow);
  display: flex; flex-direction: column; gap: 2px;
}
.topnav-menu-form { margin: 0; }
.topnav-menu-item {
  display: block; width: 100%; text-align: left;
  padding: var(--space-2) var(--space-3);
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--text-secondary); font-family: var(--font-body); font-size: 0.85rem; font-weight: 600;
  text-decoration: none; cursor: pointer;
}
.topnav-menu-item:hover { background: rgba(255, 255, 255, 0.05); color: var(--text-primary); text-decoration: none; }
.topnav-menu-item.is-active { color: var(--primary); }
/* Sign out reads as an exit action, not a peer of Settings — muted error tone. */
.topnav-menu-item.is-signout { color: var(--error); }
.topnav-menu-item.is-signout:hover { background: var(--error-bg); color: var(--error); }
.topnav-menu-sep { border: 0; border-top: 1px solid var(--border-default); margin: var(--space-1) 0; }
/* Settings gear — a visible door beside the account chip (icon-only; a fourth
   text tab would dilute the three-question nav). Comfortable touch target. */
.topnav-gear {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--radius);
  color: var(--text-secondary);
}
.topnav-gear .icon { width: 19px; height: 19px; }
.topnav-gear:hover { background: rgba(255, 255, 255, 0.05); color: var(--text-primary); }
.topnav-gear.active { color: var(--primary); }
/* "Why you" on an invite — the shortlist evidence, informational voice. */
.mission-why {
  margin: var(--space-2) 0 0;
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted);
}
/* Five tabs on ONE row (external review 2026-09-10: "Open calls" wrapped
   into two lines inside the 42px row and the scrolling row showed its bar).
   The labels never wrap and share the row's width; a slightly smaller mono
   and tighter tracking fit all five from 360px up. The scroll stays as the
   fallback below that, with its bar hidden. */
@media (max-width: 720px) {
  .topnav { padding-left: var(--space-3); padding-right: var(--space-3); }
  .topnav-nav { justify-content: space-between; gap: 0; scrollbar-width: none; }
  .topnav-nav::-webkit-scrollbar { display: none; }
  .topnav-link { flex: 1 1 auto; justify-content: center; padding: 0 var(--space-1); font-size: 0.72rem; letter-spacing: 0.02em; white-space: nowrap; }
}

/* 38. Admin dispatch — two-pane mission board -------------------------------- */
/* Filters live BESIDE the results, not above them: test → filters → matches →
   dispatch stays one continuous thought. A filter only occupies space once
   it's active (chips); the dropdowns hide behind each group header. */
.dispatch-shell { display: grid; grid-template-columns: 264px minmax(0, 1fr); gap: var(--space-5); align-items: start; margin-bottom: var(--space-5); }
.dispatch-rail { background: var(--bg-surface); border: 1px solid var(--border-default); border-radius: var(--radius-lg); padding: var(--space-4); position: sticky; top: calc(var(--topbar-height) + var(--space-4)); }
.rail-step { display: block; margin: var(--space-4) 0 var(--space-2); }
.rail-step:first-child { margin-top: 0; }
.rail-active-n { color: var(--magenta); }
.rail-pick select { width: 100%; }
.rail-pick.has-test select { border-color: var(--border-accent); background-color: var(--primary-bg); }
.rail-pick-meta { font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-top: var(--space-2); line-height: 1.5; }
.rail-group { border-top: 1px solid var(--border-default); }
.rail-group-head {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  background: none; border: none; cursor: pointer; padding: var(--space-3) 0;
  color: var(--text-secondary); font-family: var(--font-body); font-size: 0.85rem; font-weight: 600;
}
.rail-group-head:hover { color: var(--text-primary); }
.rail-group-meta { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-muted); }
.rail-group-meta b { color: var(--magenta); font-family: var(--font-mono); font-size: 0.75rem; }
.rail-chev { width: 14px; height: 14px; transition: transform 0.15s; }
.rail-group-head[aria-expanded="true"] .rail-chev { transform: rotate(180deg); }
.rail-chips { display: flex; flex-wrap: wrap; gap: 5px; padding-bottom: var(--space-3); }
.rail-chip {
  border: 1px solid var(--border-accent); background: var(--primary-bg); color: var(--primary);
  font-family: var(--font-body); font-size: 0.8rem; font-weight: 500;
  border-radius: var(--radius-sm); padding: 2px 8px; cursor: pointer;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rail-chip:hover { border-color: var(--primary); }
.rail-group-body { display: flex; flex-direction: column; gap: var(--space-3); padding-bottom: var(--space-4); }
.rail-field label { display: block; font-size: 0.78rem; color: var(--text-muted); margin-bottom: 3px; }
.rail-field select { width: 100%; }
.rail-actions { display: flex; align-items: center; gap: var(--space-3); border-top: 1px solid var(--border-default); padding-top: var(--space-3); }
.rail-clear { font-size: 0.78rem; color: var(--text-muted); }
.rail-clear:hover { color: var(--text-secondary); }

.dispatch-main { background: var(--bg-surface); border: 1px solid var(--border-default); border-radius: var(--radius-lg); overflow: hidden; }
.dispatch-mainhead { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border-default); }
.dispatch-count { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); white-space: nowrap; }
.dispatch-count b { color: var(--magenta); font-size: 1rem; margin-right: 2px; }
.dispatch-mainhead input[type="search"] { flex: 1; min-width: 0; }
.dispatch-selectall { display: flex; align-items: center; gap: 6px; font-size: 0.82rem; color: var(--text-secondary); white-space: nowrap; cursor: pointer; }
.dispatch-main .tester-pick { padding: var(--space-3) var(--space-4); position: relative; }
.dispatch-actionbar {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-strong);
  background: var(--bg-elevated);
}
.dispatch-selinfo { font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.dispatch-selinfo b { color: var(--text-primary); font-size: 0.85rem; }
.dispatch-actionbar .btn { margin-left: auto; }
.dispatch-log { margin-top: var(--space-5); }

/* Radar sweep — runs once when the match list (re)loads: search = scan. */
.scanline {
  position: absolute; left: 0; right: 0; top: 0; height: 46px; opacity: 0; pointer-events: none; z-index: 2;
  background: linear-gradient(180deg, transparent, rgba(46, 230, 255, 0.07) 55%, rgba(46, 230, 255, 0.28) 100%);
}
.scanline.go { animation: pl-sweep 0.9s ease-out forwards; }
@keyframes pl-sweep { from { transform: translateY(-46px); opacity: 1; } 85% { opacity: 1; } to { transform: translateY(340px); opacity: 0; } }

@media (max-width: 980px) {
  .dispatch-shell { grid-template-columns: 1fr; }
  .dispatch-rail { position: static; }
  .dispatch-mainhead { flex-wrap: wrap; }
}

/* Re-invite marker: tester declined this test before; dispatching re-offers it.
   Amber status voice — informational, not an error. */
.tag-reinvite { color: var(--warning); border-color: rgba(232, 176, 75, 0.35); flex-shrink: 0; }

/* Dispatch log grouped per test — one collapsed <details> row per test (the
   flat feed mixed every test together); expand to see each invite's state. */
.log-group { border-bottom: 1px solid var(--border-default); }
.log-group:last-child { border-bottom: none; }
.log-group > summary {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-1);
  cursor: pointer; list-style: none;
}
.log-group > summary::-webkit-details-marker { display: none; }
.log-group > summary:hover { background: rgba(255, 255, 255, 0.02); }
.log-chev { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-muted); transition: transform 0.15s; }
.log-group[open] .log-chev { transform: rotate(90deg); }
.log-test { font-weight: 600; font-size: 0.9rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.log-counts { margin-left: auto; font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap; }
.log-rows { padding: 0 var(--space-1) var(--space-3) calc(14px + var(--space-3)); display: flex; flex-direction: column; gap: var(--space-2); }
.log-row { display: flex; align-items: center; gap: var(--space-3); }
.log-row .user-cell { min-width: 0; flex: 1; }
.log-when { margin-left: auto; font-size: 0.78rem; color: var(--text-muted); flex-shrink: 0; }
.log-row .badge { flex-shrink: 0; }

/* 39. Themed select (combobox) ------------------------------------------------ */
/* main.js upgrades EVERY select (2026-08-20 — native popups are OS-drawn and
   render white on the team's Windows browsers, beyond CSS reach): the native
   select hides (keeps name/value for the form) and this combo renders in its
   place — same look as our inputs; long lists (>8 options or data-searchable)
   get the search box. Opt-out: [data-native] / disabled / multiple. */
select.combo-native { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.combo { position: relative; }
.combo-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
  width: 100%; padding: var(--space-3) var(--space-4); text-align: left;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-default); border-radius: var(--radius);
  color: var(--text-primary); font-family: inherit; font-size: 0.92rem; cursor: pointer;
  transition: border-color 0.15s;
}
.combo-trigger::after { content: ''; width: 7px; height: 7px; flex-shrink: 0; border-right: 1.5px solid var(--text-muted); border-bottom: 1.5px solid var(--text-muted); transform: rotate(45deg) translateY(-2px); }
.combo-trigger:hover { border-color: var(--border-strong); }
.combo-trigger[aria-expanded="true"] { border-color: var(--border-accent); }
.combo-trigger.is-placeholder { color: var(--text-muted); }
.combo-panel {
  position: absolute; z-index: 40; top: calc(100% + 4px); left: 0; right: 0;
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden;
}
.combo-search {
  width: 100%; padding: var(--space-3) var(--space-4);
  background: var(--bg-surface); border: none; border-bottom: 1px solid var(--border-default);
  color: var(--text-primary); font-family: inherit; font-size: 0.9rem; outline: none;
}
.combo-list { list-style: none; margin: 0; padding: var(--space-1); max-height: 240px; overflow-y: auto; }
.combo-list li { padding: 7px var(--space-3); border-radius: var(--radius-sm); font-size: 0.9rem; color: var(--text-secondary); cursor: pointer; }
.combo-list li:hover { background: rgba(255, 255, 255, 0.05); color: var(--text-primary); }
.combo-list li.is-active { background: var(--primary-bg); color: var(--primary); }

/* 40. Tester leaderboard ------------------------------------------------------ */
/* Broadcast top-20 beside the mission log. The viewer's row is pinned and
   highlighted even outside the top 20; anonymized entrants (privacy toggle
   off) render as "Tester #012". */
/* Rail capped at 380px so the wide shell pays the MISSION LOG, not the
   leaderboard: names stop truncating; the rail stays a rail. */
.dash-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(300px, 380px); gap: var(--space-6); align-items: start; }
.dash-side { position: sticky; top: 84px; }
.leaderboard { background: var(--bg-surface); border: 1px solid var(--border-default); border-radius: var(--radius-lg); padding: var(--space-4); }
.lb-head { display: flex; align-items: baseline; gap: var(--space-3); border-bottom: 1px solid var(--border-default); padding-bottom: var(--space-2); margin-bottom: var(--space-2); }
.lb-total { margin-left: auto; font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; }
.lb-empty { color: var(--text-muted); font-size: 0.85rem; padding: var(--space-3) 0; }
.lb-row {
  display: grid; grid-template-columns: 42px 26px minmax(0, 1fr) auto 44px; /* rank · robot avatar (s.83) · name · rated · score */
  gap: var(--space-2); align-items: center;
  padding: 7px var(--space-2); border-radius: var(--radius-sm);
}
.lb-row + .lb-row { margin-top: 1px; }
.lb-rank { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.lb-rows .lb-row:nth-child(1) .lb-rank,
.lb-rows .lb-row:nth-child(2) .lb-rank,
.lb-rows .lb-row:nth-child(3) .lb-rank { color: var(--text-primary); }
.lb-name { font-size: 0.85rem; font-weight: 550; color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-youtag { margin-left: 6px; font-family: var(--font-mono); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--primary); }
.lb-rated { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap; }
.lb-score { font-family: var(--font-mono); font-size: 0.9rem; font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.lb-row.lb-you { background: var(--primary-bg); border: 1px solid var(--border-accent); }
.lb-row.lb-you .lb-name { color: var(--text-primary); }
.lb-gap { text-align: center; color: var(--text-muted); font-family: var(--font-mono); letter-spacing: 0.3em; padding: 2px 0; }
.lb-foot { border-top: 1px solid var(--border-default); margin-top: var(--space-3); padding-top: var(--space-3); font-size: 0.82rem; color: var(--text-muted); }
.lb-pct { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--magenta); }
.lb-guide { white-space: nowrap; font-weight: 600; }
@media (max-width: 900px) {
  .dash-cols { grid-template-columns: 1fr; }
  .dash-side { position: static; }
}
/* Season/career tabs (2026-08-04). Shipped hidden; pages/dashboard.js reveals
   them and opens on the month's points race — no-JS keeps the plain career
   board. Cyan on the active tab = the one "act" control of the card. */
.lb-tabs { display: flex; gap: 2px; margin-bottom: var(--space-2); }
.lb-tabs[hidden] { display: none; }
.lb-tab {
  border: 1px solid var(--border-default); background: none; cursor: pointer;
  color: var(--text-muted); font-family: var(--font-mono); font-size: 0.72rem;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
  padding: 3px 8px; border-radius: var(--radius-sm);
}
.lb-tab:hover:not(.active) { color: var(--text-secondary); border-color: var(--border-strong); }
.lb-tab.active { color: var(--primary); border-color: var(--border-accent); background: var(--primary-bg); }
/* Tab icons (2026-08-06): calendar = the month's race, trophy = the all-time
   record — the bare words didn't read as a period switch. */
.lb-tab { display: inline-flex; align-items: center; gap: 4px; }
.lb-tab .icon { width: 12px; height: 12px; flex: none; }

/* 41. Robot moments ----------------------------------------------------------- */
/* The mascot reacts at meaningful moments (motion law: one moment, with
   meaning). Mission complete = one-shot pop + glow ramp, then the idle float.
   Invite discovery = a mini robot pops in beside the hero kicker. All of it
   collapses to a static robot under prefers-reduced-motion (§31 global kill). */
.robot-moment {
  align-items: center; justify-content: flex-start; gap: var(--space-4);
  background: var(--primary-bg);
  border-color: var(--border-accent);
}
.robot-moment .rm-mark {
  width: 54px; height: auto; flex-shrink: 0;
  filter: drop-shadow(0 0 10px rgba(46, 230, 255, 0.45));
  /* Pops in, then sits still (2026-09-02): the hero robot beside it already
     carries the page's idle float — a second floating robot was two rhythms. */
  animation: rm-pop 0.7s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.robot-moment .rm-text { flex: 1; min-width: 0; }
.robot-moment .rm-text p { margin: 2px 0 0; color: var(--text-secondary); font-size: 0.9rem; }
.robot-moment .alert-close { align-self: flex-start; }
@keyframes rm-pop {
  0%   { transform: scale(0.4) rotate(-8deg); opacity: 0; }
  60%  { transform: scale(1.12) rotate(3deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); opacity: 1; }
}
/* Mini robot beside the invite kicker. */
.kicker .rm-mini {
  display: inline-block; width: 18px; height: auto; vertical-align: -4px; margin-right: 2px;
  filter: drop-shadow(0 0 6px rgba(46, 230, 255, 0.4));
  animation: rm-pop 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
/* Robot glow = loading (the motion vocabulary's loading signal). A small mark
   whose glow breathes while an async operation is in flight — dispatch wait,
   upload/submit progress. It loops, but only while its (normally hidden)
   container is visible, i.e. while something is genuinely loading; §31 kills
   the pulse under prefers-reduced-motion. */
.robot-loading {
  display: inline-block; width: 22px; height: auto; vertical-align: -6px; margin-right: var(--space-2);
  animation: rl-glow 1.6s ease-in-out infinite;
}
@keyframes rl-glow {
  0%, 100% { filter: drop-shadow(0 0 3px rgba(46, 230, 255, 0.25)); }
  50%      { filter: drop-shadow(0 0 12px rgba(46, 230, 255, 0.65)); }
}
/* A same-load unlock folds into the submit banner as a second readout (one
   robot, one moment) — hairline-separated inside the same .rm-text. */
.robot-moment .rm-more {
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: 1px solid var(--border-default);
}
/* Unlock toast (owner round 2026-07-28): the standalone unlock moment lives
   in the lower-right corner instead of the banner slot — it stops competing
   with (and no longer suppresses) the ONE server banner above the hero.
   Green voice on the frame + kicker: an unlock is a thing you EARNED (the
   "done" meaning). Slides in once, auto-dismisses via dashboard.js; the
   global reduced-motion block flattens both transitions to instant. */
.robot-toast {
  position: fixed; right: var(--space-6); bottom: var(--space-6); z-index: 95;
  width: min(400px, calc(100vw - 2 * var(--space-6)));
  margin: 0; background: var(--bg-elevated);
  border-color: rgba(67, 217, 143, 0.4);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  animation: toast-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.robot-toast .kicker { color: var(--success); }
.robot-toast.is-leaving { opacity: 0; transform: translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease; }
@keyframes toast-in {
  from { transform: translateY(14px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}

/* 42. Site footer + legal pages --------------------------------------------- */
/* Low-chrome footer of legally-required links (privacy, terms, cookies, legal
   notice) + copyright. Included by the three role layouts, layouts/main, and the
   public auth pages. Links use the cyan "act" voice on hover only. */
.site-footer {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding: var(--space-4) var(--space-6);
  border-top: 1px solid var(--border-default);
}
.site-footer-links { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.site-footer-links a { color: var(--text-secondary); font-size: 0.82rem; text-decoration: none; }
.site-footer-links a:hover { color: var(--primary); }
.site-footer-copy { margin: 0; color: var(--text-muted); font-size: 0.78rem; }
/* Social links: quiet monochrome marks that take their real brand colour on
   hover (Discord blurple; IG/LinkedIn wordless marks go primary-text white).
   The [data-brand] reset beats the guide-card brand-colour rules (§34-adjacent),
   which would otherwise paint the Discord mark blurple at rest here. */
.site-footer-social { display: flex; align-items: center; gap: var(--space-4); }
.site-footer-social a { display: inline-flex; color: var(--text-muted); }
.site-footer-social .brand-icon { width: 16px; height: 16px; }
.site-footer-social .brand-icon[data-brand] { color: inherit; }
.site-footer-social a:hover { color: var(--text-primary); }
.site-footer-social a:hover .brand-icon[data-brand="discord"] { color: #5865F2; }
/* Controller-identity small print (register page only — the including page
   passes controllerNote to the footer partial). flex-basis 100% drops it to
   its own full-width row under the links/social/copyright line. */
.site-footer-controller { flex-basis: 100%; margin: var(--space-2) 0 0; color: var(--text-muted); font-size: 0.7rem; line-height: 1.55; }

.legal-page {
  max-width: 760px; margin: 0 auto; padding: var(--space-8) var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.legal-siblings { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-2); }
.legal-siblings a { color: var(--text-secondary); font-size: 0.82rem; }
.legal-siblings a:hover { color: var(--primary); }
.legal-siblings a.is-active { color: var(--primary); }

/* Legal document typography (views/legal/docs/*) — long-form reading inside a
   card: readable line length, clear section rhythm, quiet tables. */
.legal-doc { line-height: 1.65; color: var(--text-secondary); }
.legal-doc h2 { font-size: 1rem; color: var(--text-primary); margin: var(--space-6) 0 var(--space-2); }
.legal-doc p { margin: 0 0 var(--space-3); }
.legal-doc ul { margin: 0 0 var(--space-3); padding-left: 1.2em; }
.legal-doc li { margin-bottom: var(--space-2); }
.legal-doc strong { color: var(--text-primary); }
.legal-doc code { font-family: var(--font-mono); font-size: 0.82em; color: var(--text-primary); }
.legal-updated { font-family: var(--font-mono); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; }
.legal-table { width: 100%; border-collapse: collapse; margin: 0 0 var(--space-4); font-size: 0.88rem; }
.legal-table th { text-align: left; font-family: var(--font-mono); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); padding: var(--space-2); border-bottom: 1px solid var(--border-strong); }
.legal-table td { padding: var(--space-2); border-bottom: 1px solid var(--border-default); vertical-align: top; }

@media (max-width: 560px) {
  .site-footer { flex-direction: column; align-items: flex-start; gap: var(--space-2); padding: var(--space-4); }
}

/* 2026-08-20 (tester report #6/#7): shell-built legal navigation — the hashed
   doc templates are never edited — and the controller note as a labelled
   disclosure instead of after-the-copyright fine print. */
.legal-toc { margin: 0 0 var(--space-5); padding: var(--space-3) var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius); }
.legal-toc-title { display: block; font-family: var(--font-mono); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: var(--space-2); }
.legal-toc ul { margin: 0; padding: 0; list-style: none; columns: 2; column-gap: var(--space-6); }
.legal-toc li { margin: 0 0 6px; break-inside: avoid; }
.legal-toc a { color: var(--text-secondary); text-decoration: none; font-size: 0.85rem; }
.legal-toc a:hover { color: var(--primary); }
.legal-doc h2 { scroll-margin-top: var(--space-5); }
.legal-up {
  position: fixed; right: var(--space-4); bottom: var(--space-4); z-index: 50;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  font-family: var(--font-mono); font-size: 0.74rem; color: var(--text-secondary); text-decoration: none;
}
.legal-up:hover { color: var(--primary); border-color: var(--border-accent); }
@media (max-width: 560px) {
  .legal-toc ul { columns: 1; }
  /* ~12.75px body over a 12,500px document is tiring (report #6). */
  .legal-doc { font-size: 0.95rem; }
}
.site-footer-controller {
  flex-basis: 100%;
  margin: var(--space-3) 0 0; padding-top: var(--space-3);
  border-top: 1px solid var(--border-default);
  color: var(--text-muted); font-size: 0.76rem; line-height: 1.6; max-width: 86ch;
}
.site-footer-controller-label { display: block; font-family: var(--font-mono); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 4px; }

/* Pin the footer to the viewport bottom on short pages. The role shells are
   already min-height:100vh flex columns — the content just never claimed the
   leftover space (flex-grow only distributes surplus, so tall pages are
   unchanged). layouts/main opts in via body.footer-pinned; its error screens
   stop forcing their own 100vh there so the footer fits without scrolling. */
.topnav-shell > .content, .main-area > .content { flex: 1; }
body.footer-pinned { min-height: 100vh; display: flex; flex-direction: column; }
body.footer-pinned > .site-footer { margin-top: auto; }
body.footer-pinned .error-screen { min-height: 0; flex: 1; }

/* The ≤560px footer is a stacked column; wrap must be off there. With wrap on,
   the controller note's flex-basis:100% (a HEIGHT once direction is column)
   forced a wrap, and a wrapped column lays new columns out to the RIGHT —
   register's footer ran to 1020px on a 375px screen. */
@media (max-width: 560px) {
  .site-footer { flex-wrap: nowrap; }
  .site-footer-controller { flex-basis: auto; }
}

/* 43. Start dates & countdowns ----------------------------------------------- */
/* Live countdowns to a test's start ("Starts in 2d 4h") or deadline ("ends in
   3h 12m"). Mono data voice; under 24h remaining they turn red (.is-soon) —
   deadline pressure is the one legitimate red text. Server-rendered fallback
   text (an absolute time) shows when JS is off. */
.countdown {
  font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-secondary); white-space: nowrap;
}
.countdown.is-soon { color: var(--error); }
/* Hero variant — holds the CTA slot while the mission is locked. */
.countdown-hero {
  display: inline-flex; align-items: center;
  padding: 10px var(--space-4);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  font-size: 0.8rem; color: var(--text-primary); background: var(--bg-elevated);
}
.countdown-hero.is-soon { border-color: rgba(232, 112, 112, 0.45); }
/* Kicker-embedded end countdown inherits the kicker's mono voice. */
.kicker .countdown { font-size: inherit; letter-spacing: inherit; }
/* Hero timing chips — icon + countdown, one glanceable line under the meta
   (replaces the old crammed "starts … · respond by …" kicker text). */
.mission-timing { display: flex; flex-wrap: wrap; gap: var(--space-4); margin-top: var(--space-3); }
.timing-chip { display: inline-flex; align-items: center; gap: 6px; color: var(--text-secondary); }
.timing-chip svg { width: 14px; height: 14px; color: var(--text-muted); flex: none; }
/* Question count in the hero meta line (2026-08-13): same chip voice as the
   countdowns beside it — plain lowercase "1 question" floated mid-line. */
.q-count { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); white-space: nowrap; }

/* 44. Form-builder date quick-fill chips ------------------------------------- */
/* One-click server-time fills under the start/due datetime inputs (the native
   picker can't be pointed at another timezone for an empty field, so we seed
   the value instead). Small mono chips; cyan (act voice) on hover. */
.quickfill { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.quickfill button {
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-secondary); background: none;
  border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  padding: 6px 10px; cursor: pointer; touch-action: manipulation;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.quickfill button:hover { color: var(--primary); border-color: var(--border-accent); }
.quickfill button[data-qf="clear"]:hover { color: var(--text-muted); border-color: var(--border-strong); }

/* 45. Question section headings ---------------------------------------------- */
/* SectionHeader pseudo-questions group long forms into sections. Rendered on
   the tester feedback form (.q-section) and on every answer view — admin
   evaluation, developer feed, tester "my submission" (.answer-section). */
.q-section { margin: var(--space-5) 0 var(--space-2); }
.q-section:first-child { margin-top: 0; }
.q-section .kicker { color: var(--magenta); }
.answer-section { margin: var(--space-5) 0 var(--space-2); }
.answer-section:first-child { margin-top: 0; }
/* InfoBlock (2026-08-13): SectionHeader's multi-line sibling — admin-authored
   text/links between questions. Tester form = a quiet bordered aside; answer
   views = the same content muted (context, never competing with answers). */
.q-info {
  margin: var(--space-4) 0; padding: var(--space-3) var(--space-4);
  border-left: 2px solid var(--border-strong);
  background: rgba(255, 255, 255, 0.02);
  color: var(--text-secondary); font-size: 0.92rem;
}
.q-info p { margin: 0 0 var(--space-2); }
.q-info p:last-child, .q-info ul:last-child { margin-bottom: 0; }
.answer-info { margin: var(--space-3) 0; font-size: 0.85rem; }
.answer-info p { margin: 0 0 var(--space-1); }
/* Builder: the info-content textarea (revealed on Info-text rows). */
.q-info-input { width: 100%; resize: vertical; font-size: 0.9rem; line-height: 1.5; }

/* 46. Response follow-up notes ------------------------------------------------ */
/* Append-only notes a tester adds to their submission until it's evaluated.
   List (.comment-list) is shared by the tester view, admin evaluation, and the
   developer feed; the add-form lives only on the tester's own view. */
.comment-list { display: flex; flex-direction: column; gap: var(--space-3); }
.comment-item {
  padding: var(--space-3) var(--space-4);
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-default);
  border-left: 2px solid var(--border-strong);
  border-radius: var(--radius-sm);
}
.comment-when { display: block; font-size: 0.76rem; color: var(--text-muted); margin-bottom: var(--space-1); }
.comment-text { margin: 0; font-size: 0.9rem; color: var(--text-primary); white-space: pre-line; }
.comment-form { display: flex; flex-direction: column; gap: var(--space-3); }
.comment-list + .comment-form { margin-top: var(--space-4); }
.comment-form-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); flex-wrap: wrap; }
.comment-form-foot .form-hint { margin: 0; }

/* 47. Payments page ----------------------------------------------------------- */
/* One card per test that owes money: pending rows are selectable, paid rows
   sit below them dimmed, the action bar mirrors the dispatch board's. */
.pay-card { margin-bottom: var(--space-5); }
.pay-head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.pay-sub { display: block; margin-top: var(--space-1); font-size: 0.82rem; color: var(--text-secondary); }
.pay-table .pay-check { width: 34px; }
.pay-table input[type='checkbox'] { width: auto; }
.pay-row-paid td { opacity: 0.55; }
.pay-actionbar {
  display: flex; align-items: center; justify-content: flex-end; gap: var(--space-4);
  padding-top: var(--space-4); margin-top: var(--space-2);
  border-top: 1px solid var(--border-default);
}
.pay-selinfo { font-family: var(--font-mono); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }
.pay-selinfo b { color: var(--text-primary); }
/* Manual mark-as-paid lives one click behind (owner call 2026-08-15): a
   quiet details/summary under the table; opening it reveals the checkbox
   column (data-manual-only) and the action bar. */
.pay-manual { margin-top: var(--space-3); }
.pay-manual-summary {
  list-style: none; cursor: pointer; display: inline-block;
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); border-bottom: 1px dashed var(--border-strong); padding-bottom: 2px;
}
.pay-manual-summary::-webkit-details-marker { display: none; }
.pay-manual-summary:hover { color: var(--text-secondary); }
.pay-manual[open] .pay-manual-summary { color: var(--text-secondary); }
.pay-manual .pay-actionbar { border-top: none; padding-top: var(--space-3); margin-top: 0; }
.pay-manual-hint { margin-right: auto; font-size: 0.8rem; color: var(--text-muted); }

/* 48. Logs page ---------------------------------------------------------------- */
/* Audit / tester / error tabs over ActivityLogs; error rows expand to their
   stack trace with native <details>. */
.log-toggle-card { margin-bottom: var(--space-5); }
.log-toggle-row { display: flex; align-items: center; justify-content: space-between; gap: var(--space-5); }
.log-toggle-title { font-weight: 600; margin-bottom: var(--space-1); }
.log-toggle-title b { font-family: var(--font-mono); color: var(--text-muted); }
.log-toggle-title b.is-on { color: var(--success); }
.log-table td { vertical-align: top; }
.log-when-cell { white-space: nowrap; font-size: 0.78rem; color: var(--text-secondary); }
.log-path { font-size: 0.8rem; color: var(--text-muted); word-break: break-all; }
.log-stack > summary { cursor: pointer; list-style: none; }
.log-stack > summary::-webkit-details-marker { display: none; }
.log-error-msg { color: var(--error); }
.log-stack > summary:hover .log-error-msg { text-decoration: underline; }
.log-stack-pre {
  margin: var(--space-2) 0 0;
  padding: var(--space-3);
  background: var(--bg-base);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  font-size: 0.78rem; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word;
  max-height: 320px; overflow-y: auto;
}
.log-cap-note { margin: var(--space-3) 0 0; font-size: 0.78rem; }
@media (max-width: 720px) {
  .log-toggle-row { flex-direction: column; align-items: flex-start; }
}

/* 49. Onboarding boot sequence (tester dashboard) ------------------------------ */
.boot-seq {
  /* Breathing room on BOTH sides — without the bottom margin the card sat
     flush against the mission log / leaderboard columns below it. */
  margin: var(--space-6) 0;
  padding: var(--space-4) var(--space-5);
  background: var(--bg-card);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
}
.boot-steps { list-style: none; margin: var(--space-3) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-3); }
.boot-step { display: flex; align-items: baseline; gap: var(--space-3); font-size: 0.88rem; }
.boot-dot {
  width: 8px; height: 8px; flex-shrink: 0; align-self: center;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: transparent;
}
.boot-step.is-done .boot-dot { background: var(--success); border-color: var(--success); }
.boot-step.is-done { color: var(--text-muted); }
.boot-step.is-done span { text-decoration: none; }
.boot-note { font-size: 0.78rem; color: var(--text-muted); }
@media (max-width: 720px) {
  .boot-step { flex-wrap: wrap; }
}

/* 50. Guides (tester field manual) --------------------------------------------- */
.guide-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-5); }
.guide-card {
  display: block; padding: var(--space-5);
  background: var(--bg-card); border: 1px solid var(--border-default); border-radius: var(--radius-lg);
  text-decoration: none; color: var(--text-primary);
  transition: border-color 0.15s, transform 0.12s;
}
.guide-card:hover { border-color: var(--border-strong); transform: translateY(-2px); text-decoration: none; }
.guide-card h2 { margin: 0 0 var(--space-2); font-size: 1.05rem; } /* h3 → h2 2026-08-20 (report #9); same look */
.guide-card p { margin: 0 0 var(--space-4); color: var(--text-secondary); font-size: 0.88rem; line-height: 1.5; }
.guide-go { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--primary); }
.guide-more { margin-top: var(--space-6); font-size: 0.85rem; }
/* Cards flex-column with the link pushed to the bottom, so "Read the guide →"
   sits on one baseline across a row regardless of description length
   (owner call 2026-07-28). */
.guide-card { display: flex; flex-direction: column; align-items: flex-start; }
.guide-go { margin-top: auto; padding-top: var(--space-2); }

.guide-principle { margin-bottom: var(--space-8); } /* not --space-7: that token doesn't exist, and an undefined var() collapses the margin to 0 */
.guide-principle p { color: var(--text-secondary); line-height: 1.6; }
.guide-principle .kicker { display: block; margin-bottom: var(--space-2); }

.guide-example { margin-bottom: var(--space-8); }
.guide-q { font-size: 1.25rem; margin: 0 0 var(--space-4); }
.guide-q .kicker { display: block; margin-bottom: var(--space-1); }
.tier-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-4); align-items: start; }

.tier-card { background: var(--bg-card); border: 1px solid var(--border-default); border-radius: var(--radius-lg); overflow: hidden; }
.tier-head {
  display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-default);
}
.tier-dot { width: 8px; height: 8px; border-radius: var(--radius-pill); flex-shrink: 0; background: var(--bg-base); opacity: 0.55; }
.tier-label { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; }
.tier-score { margin-left: auto; font-size: 0.78rem; opacity: 0.75; }
/* Inverted heads (designer round 2026-07-26; was colored TEXT on the card):
   full status-color fill with dark ink — the CTA's fill+ink pattern. Legit
   here because these cards ILLUSTRATE what the quality colors mean; product
   status indicators stay dot + word. */
.tier-head { color: var(--bg-base); border-bottom: none; }
.tier-card.is-poor .tier-head { background: var(--error); }
.tier-card.is-solid .tier-head { background: var(--warning); }
.tier-card.is-elite .tier-head { background: var(--success); }
.tier-body { padding: var(--space-4); }
.tier-body p { margin: 0 0 var(--space-3); color: var(--text-secondary); font-size: 0.85rem; line-height: 1.55; }
.tier-body p:last-child { margin-bottom: 0; }
.tier-verdict {
  margin: 0; padding: var(--space-3) var(--space-4);
  border-top: 1px solid var(--border-default);
  background: rgba(255, 255, 255, 0.02);
  color: var(--text-muted); font-size: 0.78rem; line-height: 1.5;
}

.guide-tips { margin-bottom: var(--space-6); }
.guide-tips .kicker { display: block; margin-bottom: var(--space-2); }
.guide-tips-title { font-size: 1.15rem; margin: 0 0 var(--space-4); }
.guide-tip-list { margin: 0; padding-left: 1.3em; display: flex; flex-direction: column; gap: var(--space-3); }
.guide-tip-list li { color: var(--text-secondary); font-size: 0.9rem; line-height: 1.55; }
.guide-tip-list li strong { color: var(--text-primary); }
.guide-foot { font-size: 0.88rem; }

@media (max-width: 900px) {
  .tier-grid { grid-template-columns: 1fr; }
}

/* Guides index: category group headings */
.guide-cat { margin: var(--space-6) 0 var(--space-3); }
.guide-cat:first-of-type { margin-top: 0; }

/* Guide detail: step-by-step how-to blocks */
.howto { margin-bottom: var(--space-5); }
.howto-title { font-size: 1.05rem; margin: 0 0 var(--space-3); }
.howto-steps { margin: 0; padding-left: 1.5em; display: flex; flex-direction: column; gap: var(--space-2); }
.howto-steps li { color: var(--text-secondary); font-size: 0.9rem; line-height: 1.55; }
.howto-steps li::marker { font-family: var(--font-mono); font-size: 0.8rem; color: var(--text-muted); }
.howto-note { margin: var(--space-3) 0 0; padding-top: var(--space-3); border-top: 1px solid var(--border-default); color: var(--text-muted); font-size: 0.8rem; line-height: 1.5; }

/* Guide detail: tool recommendations */
.tool-list { display: flex; flex-direction: column; }
.tool-row { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-3) 0; border-bottom: 1px solid var(--border-default); }
.tool-row:last-child { border-bottom: none; }
.tool-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.tool-name { font-weight: 600; font-size: 0.92rem; }
.tool-what { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.45; }
.tool-row .btn { flex-shrink: 0; }

/* 52. Server-side pager (admin lists) ------------------------------------------ */
/* views/partials/pager.ejs — Newer/Older controls under paginated tables. */
.pager { display: flex; align-items: center; justify-content: center; gap: var(--space-4); margin-top: var(--space-5); }
.pager .pager-status { font-size: 0.78rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.08em; }
.pager .is-disabled { opacity: 0.35; pointer-events: none; }

/* Guide card icon badge */
.guide-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; margin-bottom: var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--primary-bg); border: 1px solid var(--border-accent); color: var(--primary);
}
.guide-ico .icon { width: 18px; height: 18px; }
/* Brand logo (fill-based) sits at the same size as the Lucide glyph. */
.guide-ico .brand-icon { width: 18px; height: 18px; }
.guide-ico-fallback { display: inline-flex; }
/* Brand cards get a neutral tile so the real brand colours read true instead of
   fighting the cyan accent used on the Lucide-icon cards. */
.guide-ico.has-brand { background: var(--bg-elevated); border-color: var(--border-default); }
/* Official brand colours (external constants, not palette tokens): monochrome
   brands render white on our dark tile; Discord keeps its blurple. */
.brand-icon[data-brand="steam"],
.brand-icon[data-brand="epic"] { color: #FFFFFF; }
.brand-icon[data-brand="discord"] { color: #5865F2; }
/* Optional generated icon art (public/img/guides/icons/<slug>.png). Hidden
   until main.js confirms it loaded (.has-art), then it fills the tile and the
   tinted frame drops away so the art stands on its own. */
.guide-ico img[data-guide-art] { display: none; width: 100%; height: 100%; object-fit: contain; }
.guide-ico.has-art { background: none; border-color: transparent; }
.guide-ico.has-art img[data-guide-art] { display: block; }
.guide-ico.has-art .guide-ico-fallback { display: none; }

/* Guide screenshots (figure + auto placeholder until the file is added) */
.guide-shot { margin: 0 0 var(--space-5); }
.guide-shot img { display: block; max-width: 100%; height: auto; border: 1px solid var(--border-default); border-radius: var(--radius); }
.guide-shot figcaption { margin-top: var(--space-2); font-size: 0.78rem; color: var(--text-muted); line-height: 1.45; }
.guide-shot-ph { display: none; }
.guide-shot.is-missing img { display: none; }
.guide-shot.is-missing .guide-shot-ph {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-5); border: 1px dashed var(--border-strong); border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.02); color: var(--text-muted);
}
.guide-shot-ph .icon { width: 28px; height: 28px; flex-shrink: 0; }
.guide-shot-ph span { font-size: 0.8rem; line-height: 1.5; }
.guide-shot-ph code { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text-secondary); }

/* How-to with a screenshot BESIDE the steps — most guide shots are square or
   vertical, so a right-hand column fits them better than full-width below. */
.howto-body.has-shot { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(220px, 1fr); gap: var(--space-5); align-items: start; }
.howto-aside .guide-shot { margin: 0; }
.howto-aside .guide-shot img { max-height: 420px; width: auto; max-width: 100%; }
@media (max-width: 900px) {
  .howto-body.has-shot { grid-template-columns: 1fr; }
}

/* 51. Gamification — tiers, streak, milestone, trophy case ---------------------- */
/* (The old scoreboard sub-line rules retired with the score strip, §71 —
   milestone/tier-gap detail rides the strip's tooltips now.) */

/* Rank tier word — one colour per tier, desaturated metals + the two gem tiers. */
.tier-bronze { color: #C08A5A; }
.tier-silver { color: #A9B4BD; }
/* Tier ICON (partials/tier-icon.ejs — designer round 2: the word moved into
   the hover). Inherits its tier colour from these classes via currentColor. */
.tier-icon { display: inline-flex; vertical-align: -2px; }
.tier-icon svg { width: 15px; height: 15px; }
.tier-gold { color: var(--warning); }
.tier-platinum { color: #8FD3D9; }
.tier-diamond { color: var(--primary); }

/* Trophy case (side rail, under the leaderboard) */
.trophy-case { margin-top: var(--space-5); padding: var(--space-4) var(--space-5); background: var(--bg-card); border: 1px solid var(--border-default); border-radius: var(--radius-lg); }
.tc-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--space-4); }
.tc-count { font-size: 0.78rem; color: var(--text-muted); }
.tc-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-3); }
.tc-badge { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); text-align: center; border-radius: var(--radius-sm); }
.tc-badge:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.tc-art {
  position: relative;
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, 0.03); border: 1px solid var(--border-default);
}
.tc-art img { display: none; width: 100%; height: 100%; object-fit: contain; border-radius: var(--radius-sm); }
.tc-art.has-art img { display: block; }
.tc-art.has-art .tc-ico { display: none; }
.tc-ico { display: inline-flex; color: var(--text-secondary); }
.tc-ico .icon { width: 20px; height: 20px; }
.tc-label { font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); line-height: 1.3; }
/* Earned = lit; locked = dimmed silhouette. */
.tc-badge.is-earned .tc-art { border-color: var(--border-accent); background: var(--primary-bg); }
.tc-badge.is-earned .tc-ico { color: var(--primary); }
.tc-badge.is-earned .tc-label { color: var(--text-secondary); }
.tc-badge.is-locked { opacity: 0.45; }
.tc-badge.is-locked .tc-art img { filter: grayscale(1); }
/* Score-trend sparkline (now lives in the score strip, §71). Informational
   voice — muted strokes, no cyan (the data informs, it doesn't act). */
.trend-spark polyline { stroke: var(--text-muted); stroke-width: 1.5; stroke-linejoin: round; stroke-linecap: round; }
.trend-spark circle { fill: var(--text-secondary); }
/* Just-earned trophy: one-shot spring pop + glow flare, applied by the unlock
   script as part of the SAME celebration moment as the banner (never loops;
   §31 flattens it under prefers-reduced-motion). */
.tc-badge.is-new .tc-art { animation: tc-unlock 0.9s cubic-bezier(0.34, 1.56, 0.64, 1) both; }
@keyframes tc-unlock {
  0%   { transform: scale(0.5); filter: drop-shadow(0 0 0 rgba(46, 230, 255, 0)); }
  55%  { transform: scale(1.22); filter: drop-shadow(0 0 14px rgba(46, 230, 255, 0.7)); }
  100% { transform: scale(1); filter: drop-shadow(0 0 6px rgba(46, 230, 255, 0.35)); }
}
/* Badge detail popover (owner round 2026-07-28): each .tc-badge is now a
   details element; the summary (.tc-face) is the old badge face, the panel
   shows desc + earned/progress. Locked badges lift to full opacity while
   open so the popup itself is readable. */
.tc-badge { position: relative; }
.tc-badge > summary { list-style: none; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: var(--space-2); text-align: center; border-radius: var(--radius-sm); }
.tc-badge > summary::-webkit-details-marker { display: none; }
.tc-badge > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.tc-badge[open] { opacity: 1; }
.tc-pop {
  position: absolute; top: calc(100% + 6px); right: -8px; z-index: 30;
  width: 200px; text-align: left;
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: var(--space-3);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.45);
}
.tc-pop-title { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-primary); }
.tc-pop-desc { margin: var(--space-1) 0 0; font-size: 0.8rem; line-height: 1.45; color: var(--text-secondary); }
.tc-pop-state { margin-top: var(--space-2); font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.tc-pop-state.is-done { color: var(--success); } /* earned = the done voice */
.tc-bar { margin-top: var(--space-2); height: 3px; background: rgba(255, 255, 255, 0.07); border-radius: var(--radius-pill); overflow: hidden; }
.tc-bar span { display: block; height: 100%; background: var(--text-secondary); border-radius: var(--radius-pill); }
/* Mobile: a 200px popup anchored to a ~70px cell clips off-screen on the
   left column (verified at 375px). Anchor it to the CASE instead and span
   the full width — it opens in flow position under the tapped badge. */
@media (max-width: 720px) {
  .trophy-case { position: relative; }
  .tc-badge { position: static; }
  .tc-pop { left: 0; right: 0; width: auto; top: auto; margin-top: var(--space-2); }
}
/* Robot avatars (2026-08-16): the popover names the cosmetic the achievement
   unlocks; once earned, "Wear it" is the cyan act to the Settings picker. */
.tc-pop-unlock { margin-top: var(--space-2); font-size: 0.8rem; color: var(--text-secondary); }
.tc-pop-unlock-k { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-right: 2px; }
.tc-pop-unlock-slot { color: var(--text-muted); }
.tc-pop-wear { margin-left: var(--space-1); font-family: var(--font-mono); font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; white-space: nowrap; }

/* 53. Dispatch match score ------------------------------------------------------ */
/* Ranked "Suggested" ordering on the dispatch board: score + leading reason on
   each candidate row, full factor breakdown in the title tooltip. Informational
   voice only (no cyan — the score informs, it doesn't act). */
.segmented-sm { padding: 3px; }
.segmented-sm a { padding: 4px 10px; font-size: 0.75rem; }
.match-cell {
  margin-left: auto; flex-shrink: 0;
  display: flex; flex-direction: column; align-items: flex-end; gap: 2px;
  text-align: right; min-width: 0;
}
.match-score { font-size: 0.95rem; font-weight: 700; color: var(--text-primary); line-height: 1; }
.match-why {
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px;
}

/* 54. Feedback-quality flag (admin responses) ----------------------------------- */
/* Low-effort triage hint: status voice = dot + word (never a filled pill), amber
   because it asks for attention without judging — the human sets the score. */
.qflag {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--warning); white-space: nowrap;
}
.qflag-dot { width: 7px; height: 7px; border-radius: var(--radius-pill); background: var(--warning); flex-shrink: 0; }
.qflag-panel {
  margin-bottom: var(--space-4); padding: var(--space-3) var(--space-4);
  background: rgba(232, 176, 75, 0.06); border: 1px solid rgba(232, 176, 75, 0.25); border-radius: var(--radius-sm);
}
.qflag-reasons { margin: var(--space-2) 0 0; padding-left: var(--space-5); color: var(--text-secondary); font-size: 0.85rem; }
.qflag-reasons li { margin-bottom: 2px; }
.qflag-hint { margin-top: var(--space-2); font-size: 0.75rem; color: var(--text-muted); }

/* 55. Gamer profile — section TOC, max-N chips, optional tags -------------------- */
/* The expanded profile reads as 7 small wins: the aside lists each section with
   a tick and an n/m count; long chip groups dim once their pick limit is hit. */
.profile-toc { margin: var(--space-4) 0; border-top: 1px solid var(--border-default); }
.toc-row {
  display: flex; align-items: center; gap: var(--space-2);
  padding: 7px 2px; border-bottom: 1px solid var(--border-default);
  text-decoration: none; color: var(--text-secondary); font-size: 0.85rem;
}
.toc-row:hover { color: var(--text-primary); }
.toc-tick {
  width: 16px; height: 16px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  color: transparent;
}
/* The tick svg is ALWAYS in the markup (realtime ring toggles rows by class
   flip, never markup injection) — visible only once the section is done. */
.toc-tick svg { width: 10px; height: 10px; display: none; }
.toc-row.is-done .toc-tick { border-color: var(--success); color: var(--success); }
.toc-row.is-done .toc-tick svg { display: block; }
.toc-row.is-done .toc-label { color: var(--text-muted); }
.toc-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.toc-count { font-size: 0.74rem; color: var(--text-muted); }
.toc-extras { margin: 0 0 var(--space-3); font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
/* Sections scroll under the sticky topbar with breathing room. */
.profile-section { scroll-margin-top: 84px; }
/* "pick up to 3" + optional markers — quiet mono voice, never a filled pill. */
.mono-note, .opt-tag {
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-muted); font-weight: 400;
}
/* Chip group at its pick limit: unchecked chips dim (their inputs are disabled). */
.chip-group.is-maxed .chip:has(input:not(:checked)) { opacity: 0.4; cursor: not-allowed; }
/* Dispatch-row marker for the selection policy: incomplete profile = amber tag. */
.tag-incomplete { color: var(--warning); border-color: rgba(232, 176, 75, 0.35); flex-shrink: 0; }
/* Collapsible profile sections (2026-07-13): each card is a <details>; only the
   first unfinished section opens, the rest read as one-line headers with a
   count — the answer to "the profile punches you with text". */
details.profile-section > summary.section-head {
  margin: 0; padding: var(--space-4) var(--space-5);
  border-bottom: none; cursor: pointer; align-items: center;
  list-style: none;
}
details.profile-section > summary.section-head::-webkit-details-marker { display: none; }
details.profile-section[open] > summary.section-head { border-bottom: 1px solid var(--border-default); }
details.profile-section > .card-body { padding-top: var(--space-5); }
details.profile-section > summary.section-head:hover .section-name { color: var(--primary); }
.section-count { margin-left: auto; font-size: 0.76rem; color: var(--text-muted); flex-shrink: 0; }
.section-count.is-done { color: var(--success); }
.section-chev { width: 16px; height: 16px; margin-left: var(--space-3); color: var(--text-muted); flex-shrink: 0; transition: transform 0.15s ease; }
details.profile-section[open] > summary.section-head .section-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .section-chev { transition: none; } }

/* 56. Required questions (feedback form + form-builder) -------------------------- */
/* Required marker = quiet mono word next to the type label; a blocked submit
   marks the gaps amber until they're answered. */
/* Right-aligned "Required" marker on the question row — a neutral outlined tag,
   NOT a warning colour (amber read as an error). The amber only appears on
   .q-missing when a submit is actually blocked. */
.q-req {
  flex-shrink: 0; margin-top: 2px;
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  padding: 2px 7px;
}
.q-block.q-missing .q-req { color: var(--warning); border-color: rgba(232, 176, 75, 0.45); }
.q-block.q-missing { border-left: 2px solid var(--warning); padding-left: var(--space-4); margin-left: calc(-1 * var(--space-4) - 2px); }
/* The answered meter doubles as "jump to first unanswered" — button reset. */
button.progress-text { background: none; border: none; padding: 0; cursor: pointer; font: inherit; }
button.progress-text:hover { color: var(--text-primary); text-decoration: underline; text-underline-offset: 3px; }
/* Builder: the per-row Required select stays narrow next to the type select. */
.builder-row select.q-required { max-width: 110px; flex-shrink: 0; }

/* 57. Admin chrome: test-card overflow menu + sidebar work badges ---------------- */
/* Rare card actions (Duplicate, Deactivate) live one click behind a ⋯ menu —
   a red button on every card diluted the danger voice across the grid. */
.card-menu { position: relative; margin-left: auto; }
.card-menu > summary { list-style: none; line-height: 1; }
.card-menu > summary::-webkit-details-marker { display: none; }
.card-menu-panel {
  position: absolute; right: 0; top: calc(100% + 4px); z-index: 30;
  min-width: 150px; padding: 4px;
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}
.card-menu-panel form { margin: 0; }
.menu-item {
  display: block; width: 100%; padding: 8px 10px; text-align: left;
  background: none; border: none; border-radius: var(--radius-sm);
  color: var(--text-secondary); font-size: 0.85rem; cursor: pointer;
}
.menu-item:hover { background: rgba(255, 255, 255, 0.05); color: var(--text-primary); }
.menu-item.is-danger { color: var(--error); }
.menu-item.is-danger:hover { background: rgba(232, 112, 112, 0.08); color: var(--error); }
/* Sidebar "work waiting" badges — magenta (the "live count" voice), quiet
   outline not a filled pill. */
.nav-badge {
  margin-left: auto; font-size: 0.72rem; font-weight: 700;
  color: var(--magenta); border: 1px solid rgba(255, 92, 165, 0.4);
  border-radius: var(--radius-sm); padding: 1px 6px; flex-shrink: 0;
}
/* Developer projects: closed/inactive group dims like the admin tests page. */
.card-grid.is-stopped .project-card { opacity: 0.75; }
.card-grid.is-stopped .project-card:hover { opacity: 1; }

/* 58. Mobile pass (~375px) -------------------------------------------------------- */
/* Admin tables: columns must never crush into character soup on a narrow
   viewport — .table-wrap's horizontal scroll is the mobile affordance, so give
   the table a width floor and let the wrapper scroll. width:100% keeps desktop
   unchanged (the floor only bites when the container is narrower). */
.table-wrap .table { min-width: 560px; }
/* §10's word-break:break-all told table layout the email can break after EVERY
   character, so auto-layout starved the identity column into character soup
   (26px) even with room to scroll. break-word wraps a truly-unbreakable value
   without shrinking the column's min-content width. */
.user-cell-sub { word-break: normal; overflow-wrap: break-word; }

@media (max-width: 640px) {
  /* Dispatch candidate rows: name + meta own the first line; the profile tag
     and the match score wrap onto their own line instead of overlapping the
     meta text and clipping at the card edge. */
  .tester-pick label { flex-wrap: wrap; }
  /* A percentage basis (not 0) so flex LINE-BREAKING already sees the info
     column as wide — checkbox+avatar+info fill line one, tag + match score
     drop to a second line (match-cell's margin-left:auto right-aligns it). */
  .tester-pick .tester-info { flex: 1 1 55%; }
  /* Page headers (title + stat pair or action button): the right-hand block
     drops below the title instead of squeezing beside it. */
  .page-header { flex-wrap: wrap; }
  .resp-summary-stat { align-items: flex-start; }
  .resp-summary-stat .lbl { white-space: nowrap; }
}

/* 59. Rewards ledger (tester) ---------------------------------------------------- */
/* Summary pair (Paid out / Pending). Self-contained rs-* classes — this block
   originally borrowed the dashboard scoreboard's classes and silently unstyled
   when those were retired with the hero redesign (2026-07-28 fix). */
.rewards-summary {
  margin-top: var(--space-5); max-width: 460px;
  display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
  background: var(--border-default); border: 1px solid var(--border-default);
  border-radius: var(--radius); overflow: hidden;
}
.rs-cell { background: var(--bg-surface); padding: var(--space-4) var(--space-5); }
.rs-k {
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-secondary);
}
.rs-v { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 1.9rem; font-weight: 700; line-height: 1.2; margin-top: 2px; }
.rs-v.is-money { color: var(--success); }
.rs-v.is-blank { color: var(--text-muted); }
.rs-sub { margin-top: var(--space-1); font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }

/* 60. Rich brief (stylized test descriptions) -------------------------------------- */
/* Block spacing for renderBrief output inside .test-desc (feedback form +
   detail modal). The kicker reuses the global magenta voice; strong lifts to
   primary text so bold reads on the dark card. */
.test-desc p { margin: 0 0 var(--space-3); }
.test-desc p:last-child { margin-bottom: 0; }
.test-desc ul { margin: 0 0 var(--space-3); padding-left: 1.25em; }
.test-desc ul:last-child { margin-bottom: 0; }
.test-desc li { margin: 3px 0; }
.test-desc .rt-kicker { margin: var(--space-4) 0 var(--space-2); }
.test-desc .rt-kicker:first-child { margin-top: 0; }
.test-desc strong { color: var(--text-primary); }
/* Format hint under the form-builder description field. */
.form-hint code {
  font-family: var(--font-mono); font-size: 0.78em;
  background: rgba(255, 255, 255, 0.06); border-radius: var(--radius-sm); padding: 1px 5px;
}

/* 61. Danger zone (delete account) -------------------------------------------------- */
/* The one red-bordered card in Settings — sits last, reads as a boundary. */
.danger-zone { border-color: rgba(232, 112, 112, 0.45); }
.danger-zone .card-header { border-bottom-color: rgba(232, 112, 112, 0.25); }
.danger-zone .card-title { color: var(--error); }
.danger-form { max-width: 420px; margin-top: var(--space-4); }
.danger-ack {
  display: flex; align-items: flex-start; gap: var(--space-2);
  margin-bottom: var(--space-4); font-size: 0.9rem; color: var(--text-secondary);
}
.danger-ack input { width: auto; margin-top: 3px; }

/* 62. Vacation mode ------------------------------------------------------------------ */
/* Standing away banner on the dashboard: message left, one-click return right. */
.away-banner { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
.away-banner .inline-form { margin: 0; }
/* Amber topnav chip while away — the state stays visible on every page. */
.topnav-awaychip {
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--warning); border: 1px solid rgba(232, 176, 75, 0.45);
  border-radius: var(--radius-sm); padding: 3px 8px; text-decoration: none;
}
.topnav-awaychip:hover { background: rgba(232, 176, 75, 0.08); text-decoration: none; }
/* Dispatch header: how many testers vacation mode is hiding. */
.away-note { color: var(--text-muted); font-weight: 400; }

/* 63. Decline reasons ---------------------------------------------------------------- */
/* Two-beat decline on the hero invite: the Decline button is a details
   summary; the reason menu pops out UNDER it (each reason is the submit).
   Reuses the ⋯-menu item voice; no standing furniture in the action row. */
.decline-menu { position: relative; display: inline-block; }
.decline-menu > summary { list-style: none; }
.decline-menu > summary::-webkit-details-marker { display: none; }
.decline-menu-panel {
  position: absolute; left: 0; top: calc(100% + 4px); z-index: 30;
  min-width: 220px; padding: 4px;
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}
.decline-menu-hint {
  padding: 6px 10px 4px;
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted);
}
.decline-skip { color: var(--text-muted); }

/* 64. Form-builder template blocks ---------------------------------------------------- */
/* "+ Insert question block" popout beside "+ Add question" — same popout
   anatomy as the decline menu / card menu. */
.builder-actions { display: flex; align-items: center; gap: var(--space-2); }
.tpl-menu { position: relative; display: inline-block; }
.tpl-menu > summary { list-style: none; }
.tpl-menu > summary::-webkit-details-marker { display: none; }
.tpl-menu-panel {
  position: absolute; left: 0; bottom: calc(100% + 4px); z-index: 30;
  min-width: 250px; padding: 4px;
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}
.tpl-menu-hint {
  padding: 6px 10px 4px;
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted);
}
.tpl-menu-panel .menu-item { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); }
.tpl-count { font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); }

/* 65. QA polish round (2026-07-14) --------------------------------------------------- */
/* Gaming IDs: the honest "I don't use any of these" declaration. */
.id-none { margin-bottom: var(--space-5); padding-bottom: var(--space-4); border-bottom: 1px solid var(--border-default); }
.id-none .form-hint { margin-top: var(--space-2); }
#sec-ids .id-platform input:disabled { opacity: 0.45; cursor: not-allowed; }
/* Guide screenshot lightbox — click to expand, click/Esc to close. */
.guide-shot img { cursor: zoom-in; }
.shot-lightbox {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  background: rgba(6, 7, 8, 0.9); cursor: zoom-out; padding: var(--space-5);
}
.shot-lightbox img {
  max-width: min(1400px, 94vw); max-height: 92vh;
  border: 1px solid var(--border-strong); border-radius: var(--radius);
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);
}
/* Settings switches: one-shot green "saved" flare after a reload-free save
   (dies under prefers-reduced-motion via the section-31 global kill). */
.switch.is-saved .slider { animation: switch-saved 0.7s ease-out both; }
@keyframes switch-saved {
  0%   { box-shadow: 0 0 0 0 rgba(67, 217, 143, 0.55); }
  100% { box-shadow: 0 0 0 8px rgba(67, 217, 143, 0); }
}

/* 66. Reviewer note (my-submission) --------------------------------------------------- */
/* The admin's "why this score" coaching line, shown under the score once evaluated. */
.reviewer-note {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid var(--border-default);
  border-left: 2px solid var(--border-strong);
  border-radius: var(--radius-sm);
}
.reviewer-note .kicker { margin-bottom: var(--space-2); }
.reviewer-note-text { margin: 0; font-size: 0.9rem; color: var(--text-primary); white-space: pre-line; }

/* 67. Steam library (settings) -------------------------------------------------------- */
/* Rides the provider-row vocabulary; steam-row is its own flex shell because the
   linked state carries stats + two action forms instead of one badge/button. */
.steam-row { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); }
.steam-row .provider-ico svg { width: 22px; height: 22px; }
.steam-stats { font-size: 0.8rem; color: var(--text-primary); margin: var(--space-1) 0; }
.steam-private { color: var(--warning); }
.steam-actions { display: flex; gap: var(--space-2); flex-shrink: 0; }
@media (max-width: 640px) {
  .steam-row { flex-direction: column; }
  .steam-actions { width: 100%; }
}
/* 2026-08-19: the card lives in the profile's Gaming IDs section now (lawyer's
   manual step 10) — two labelled blocks, connect (layer 1) and read (layer 3).
   .provider-name was only scoped under .provider-row; give the steam row its own. */
.steam-row .provider-name { display: flex; align-items: flex-start; gap: var(--space-3); font-weight: 600; min-width: 0; }
.id-platform.steam-connect, .id-platform.steam-read { padding-top: var(--space-4); }
.id-platform.steam-read .section-intro { margin-bottom: var(--space-3); }
/* The manual-IDs part starts after the Steam blocks: the "I don't use any of
   these" rule gets a top gap + hairline so it reads as the next group, not
   as part of the read-consent question above it (owner 2026-08-19). */
.id-platform.steam-read + .id-none { margin-top: var(--space-6); padding-top: var(--space-5); border-top: 1px solid var(--border-default); }
/* Long intros collapse to their first paragraph + a disclosure (his rule:
   visible, collapsible — never hover). */
.section-intro .form-more { margin-top: var(--space-1); }
.section-intro .form-more p { font-size: inherit; }

/* 68. Steam evidence on the dispatch board -------------------------------------------- */
/* Verified playtime in the test game's genres, shown BESIDE the match score —
   deliberately quieter than it (context for the admin, not part of the ranking). */
.steam-cell { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; flex-shrink: 0; margin-left: var(--space-3); }
.steam-hours { font-size: 0.82rem; font-weight: 600; color: var(--text-primary); }
.steam-why {
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--text-muted); white-space: nowrap;
}
.steam-cell.steam-none .steam-hours { color: var(--text-muted); font-weight: 500; }
/* Owning the game under test is the strongest signal on the row — the one
   place Steam evidence earns the accent border. */
.tag-owns { border-color: var(--border-accent); color: var(--primary); }
.rail-steam { margin-top: var(--space-1); }
@media (max-width: 640px) {
  .steam-cell { align-items: flex-start; margin-left: 0; }
}
/* Users-page Steam readout (same section family as the dispatch evidence). */
.steam-mini { font-size: 0.78rem; white-space: nowrap; color: var(--text-secondary); }

/* 69. Steam detail popup (admin: what a tester plays) --------------------------------- */
/* Clickable Steam evidence — a button styled as the plain readout it replaces. */
.steam-link, .steam-evidence {
  background: none; border: none; padding: 0; margin: 0; font: inherit;
  cursor: pointer; color: inherit; text-align: inherit;
}
.steam-evidence { display: inline-flex; align-items: center; gap: var(--space-2); }
.steam-link { border-bottom: 1px dotted var(--border-strong); }
.steam-link:hover, .steam-evidence:hover { color: var(--primary); }
.steam-link:hover .steam-hours, .steam-evidence:hover .steam-hours { color: var(--primary); }
.steam-modal { max-width: 560px; }
.sdm-genres { display: flex; flex-wrap: wrap; gap: var(--space-2); margin: var(--space-2) 0 var(--space-4); }
.sdm-chip {
  font-family: var(--font-mono); font-size: 0.78rem; padding: 3px 8px;
  border: 1px solid var(--border-default); border-radius: var(--radius-sm);
  color: var(--text-secondary); background: var(--bg-elevated);
}
.sdm-chip.sdm-genre-match { border-color: var(--border-accent); color: var(--primary); }
.sdm-chip.sdm-empty { color: var(--text-muted); }
.sdm-lib-kicker { margin-top: var(--space-3); }
/* scrollbar-gutter keeps the track out of the text flow (the hours column ran
   under it); the padding stops rows touching the scrollbar when one appears. */
.sdm-lib {
  display: flex; flex-direction: column; margin-top: var(--space-2);
  max-height: 42vh; overflow-y: auto;
  scrollbar-gutter: stable; padding-right: var(--space-3);
}
.sdm-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3);
  padding: 5px 0; border-bottom: 1px solid var(--border-default);
}
.sdm-row-match .sdm-game { color: var(--primary); }
.sdm-game { font-size: 0.85rem; color: var(--text-primary); }
.sdm-hrs { font-size: 0.75rem; color: var(--text-secondary); white-space: nowrap; }
.sdm-private { color: var(--warning); border-color: var(--warning); }

/* 70. Question library chips (form-builder) ------------------------------------------- */
/* Identity, not status: neutral bordered chips. "library" reads slightly
   stronger than "custom" so the demotion is visible when an edit severs it. */
.lib-chip {
  align-self: center; flex-shrink: 0;
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500;
  text-transform: uppercase; letter-spacing: 0.06em;
  padding: 2px 7px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-default); color: var(--text-muted);
  background: var(--bg-elevated); white-space: nowrap;
}
.lib-chip-library { border-color: var(--border-strong); color: var(--text-secondary); }

/* 71. Score strip (tester dashboard) -------------------------------------------------- */
/* One quiet mono line under the hero band — the stats stopped competing with
   the CTA (designer round 2026-07-26). No count-up: the screen's one moment
   belongs to the hero. Cells with nothing to say don't render. */
.score-strip {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-5);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--border-default); border-radius: var(--radius);
  margin-bottom: var(--space-8);
  font-family: var(--font-mono); font-size: 0.78rem;
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted);
}
.ss-item { display: inline-flex; align-items: center; gap: 6px; color: inherit; text-decoration: none; }
.ss-n { color: var(--text-primary); font-weight: 700; font-variant-numeric: tabular-nums; }
a.ss-item:hover .ss-n, a.ss-item:hover { color: var(--primary); }
.ss-money .ss-n { color: var(--success); }
.ss-live { color: var(--magenta); }
.ss-trend { line-height: 0; }
.ss-streak { margin-left: auto; color: var(--success); }
@media (max-width: 720px) {
  .score-strip { gap: var(--space-4); font-size: 0.72rem; }
  .ss-streak { margin-left: 0; }
}

/* 72. Unsaved-changes marker (profile aside) ------------------------------------------ */
/* The completion ring previews live, so the page must say plainly that nothing
   is stored yet. Warning voice (a state to resolve), sits above Save. */
.unsaved-flag {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 0 0 var(--space-3); padding: var(--space-3);
  border: 1px solid rgba(232, 176, 75, 0.35); border-radius: var(--radius-sm);
  background: rgba(232, 176, 75, 0.08);
  font-size: 0.78rem; line-height: 1.45; color: var(--warning);
}
.unsaved-flag svg { width: 15px; height: 15px; flex-shrink: 0; margin-top: 1px; }

/* 73. Hero tactical grid backdrop (tester dashboard) -------------------------- */
/* Hero-actor round 2026-08-07: the .hero-grid glyph field is RETIRED — both
   hero states (active and empty) now render the §81 mission-control scene
   (.mc-stage), robot included. The hero-draw keyframes live on (§81's
   draw-ins ride them), as do the living-robot expressions below. */
/* overflow:hidden moved DOWN to .mc-stage (2026-08-13): on the hero it also
   amputated the decline popout, which drops below the hero's border. The
   stage is inset:0 — the same box — so the scene crops identically. */
.mission-hero { position: relative; }
.mission-hero > :not(.mc-stage) { position: relative; z-index: 1; }
@keyframes hero-draw { to { stroke-dashoffset: 0; } }

/* Empty state: the typed scan line whose cursor is the sanctioned perpetual
   loop (main.js retypes it when motion is allowed). The old sparse glyph
   field retired with .hero-grid (hero-actor round 2026-08-07). */
/* The sonar ping (::after ring + hero-ping keyframes) was buried 2026-08-06
   (mission-control round): the empty hero's search signal is now the radar
   console's one-shot sweep in §81 — one search signal, not two. */
.hero-term {
  font-family: var(--font-mono); font-size: 0.78rem;
  color: var(--text-muted); margin: var(--space-4) 0 0;
}

/* The living robot (owner round 2026-07-28): the mark is an inline-SVG twin
   of the PNG (views/partials/robot-mark.ejs) so the face can animate. All of
   it rides the motion law's idle-robot loop allowance: float + a ~6s blink
   everywhere, scanning eyes on the empty state (the robot IS searching), and
   a one-shot grin pop when an invite lands. Gradients/bloom INSIDE the svg
   are the brand asset itself (identical to what the PNG bakes in), not UI
   chrome. The global reduced-motion block kills every one of these. */
.robot-mark .rb-eye, .robot-mark .rb-gaze, .robot-mark .rb-smile {
  transform-box: fill-box; transform-origin: center;
}
.robot-mark .rb-eye { animation: rb-blink 5.6s linear infinite; }
@keyframes rb-blink {
  0%, 93%, 100% { transform: scaleY(1); }
  95%, 97.5% { transform: scaleY(0.08); }
}
/* Standing by = the eyes sweep the room. */
.mission-hero.is-empty .rb-gaze { animation: rb-scan 7s ease-in-out infinite; }
@keyframes rb-scan {
  0%, 12%, 88%, 100% { transform: translateX(0); }
  32%, 45% { transform: translateX(-7px); }
  60%, 74% { transform: translateX(7px); }
}
/* A fresh invite = one quick grin, then back to composure. */
.mission-hero.is-invite .rb-smile { animation: rb-grin 0.9s ease-out 0.5s; }
@keyframes rb-grin {
  0%, 100% { transform: scale(1); }
  45% { transform: scale(1.3); }
}
/* The active hero's robot positioning moved to §81 (mission-control round
   2026-08-06) — the robot now lives inside .mc-stage with the scene it
   operates. Expressions above (grin/proud/drowsy/blink) still apply: they
   select through .mission-hero, and the robot is still in its subtree. */

/* Expressions round 2 (owner round 2026-07-28): proud + drowsy. Both are
   STATIC faces (no new loops) driven by state classes the view stamps on the
   hero — is-proud on the post-submit load, is-drowsy while the tester is
   away. Drowsy replaces the idle blink with a slower half-lidded one, and
   honesty: an away robot is NOT searching, so the empty state's scan-eyes
   stop and the typed line says the scan is suspended (view-side). */
.mission-hero.is-proud .rb-smile { transform: scale(1.28); }
/* Asleep, not idling: NO blink at all — lids hold half-closed, eyes still. */
.mission-hero.is-drowsy .rb-eye { animation: none; transform: scaleY(0.5); }
.mission-hero.is-drowsy .rb-gaze { animation: none; }
/* The sleep-breath: a z z z train rising off the head, fading as it climbs.
   It loops, but it IS the idle robot's loop (same allowance as the float);
   base opacity is 0, so reduced motion simply never shows a z. */
.robot-mark .rb-zzz { display: none; }
.mission-hero.is-drowsy .rb-zzz { display: block; }
.rb-zzz text {
  font-family: var(--font-mono); font-weight: 700; fill: var(--text-secondary);
  opacity: 0; transform-box: fill-box; transform-origin: center;
  animation: rb-zzz 3.9s ease-out infinite;
}
.rb-zzz text:nth-child(2) { animation-delay: 1.3s; }
.rb-zzz text:nth-child(3) { animation-delay: 2.6s; }
@keyframes rb-zzz {
  0%   { opacity: 0; transform: translate(0, 0); }
  12%  { opacity: 0.65; }
  70%  { opacity: 0; transform: translate(12px, -24px); }
  100% { opacity: 0; transform: translate(12px, -24px); }
}
/* The empty-state robot's in-flow sizing (96px base + 128px hero override)
   retired 2026-08-07 (hero-actor round): the robot now sits INSIDE the §81
   scene svg, sized in viewBox units by the view. */

/* Match-readiness panel (owner round 2026-07-28): the empty state's odds
   checklist. Dot + word voice (§ status rules) — green = a real dispatch
   input satisfied, amber = one you can improve, fix one click away. */
.ready-panel { margin: var(--space-4) 0 0; max-width: 520px; }
.ready-panel .kicker { display: block; margin-bottom: var(--space-2); }
.ready-row {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-2) 0; font-size: 0.85rem; color: var(--text-secondary);
  border-bottom: 1px solid var(--border-default);
}
.ready-row:last-child { border-bottom: none; }
.ready-text { flex: 1; min-width: 0; }
.ready-row a { font-family: var(--font-mono); font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; white-space: nowrap; }

/* 74. Feedback dojo (guides) -------------------------------------------------- */
/* Three pick-one drills graded client-side (pages/dojo.js). Option cards use
   the informational voice until graded: green hairline = the actionable
   answer, red hairline only on a wrong PICK (your choice, not a verdict on
   you — undimmed so the why stays readable). */
.dojo-score { font-size: 0.8rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.07em; margin: 0 0 var(--space-4); }
.dojo-drill { margin-bottom: var(--space-5); }
.dojo-setup { margin: var(--space-2) 0 0; }
.dojo-opts { display: grid; gap: var(--space-3); margin-top: var(--space-4); }
.dojo-opt {
  display: flex; align-items: flex-start; gap: var(--space-3); text-align: left;
  background: rgba(255, 255, 255, 0.02); border: 1px solid var(--border-default);
  border-radius: var(--radius); padding: var(--space-3) var(--space-4);
  color: var(--text-secondary); font-family: var(--font-body); font-size: 0.9rem; line-height: 1.5;
  cursor: pointer; transition: border-color 0.15s;
}
.dojo-opt:hover:not(:disabled) { border-color: var(--border-strong); }
.dojo-opt:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.dojo-opt:disabled { cursor: default; }
.dojo-letter {
  flex-shrink: 0; font-family: var(--font-mono); font-size: 0.74rem; font-weight: 700;
  width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm); color: var(--text-muted);
}
.dojo-body { min-width: 0; }
.dojo-why { display: block; margin-top: var(--space-2); font-size: 0.8rem; color: var(--text-muted); }
.dojo-opt.is-good { border-color: rgba(67, 217, 143, 0.45); }
.dojo-opt.is-good .dojo-letter { border-color: rgba(67, 217, 143, 0.45); color: var(--success); }
.dojo-opt.is-bad { opacity: 0.65; }
.dojo-opt.is-picked.is-bad { opacity: 1; border-color: rgba(232, 112, 112, 0.45); }
.dojo-opt.is-picked.is-bad .dojo-letter { border-color: rgba(232, 112, 112, 0.45); color: var(--error); }
.dojo-answer { margin-top: var(--space-4); font-size: 0.88rem; color: var(--text-secondary); }
.dojo-answer summary { cursor: pointer; font-family: var(--font-mono); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted); }
.dojo-answer p { margin: var(--space-2) 0 0; }

/* Checkpoint card at the end of the feedback guide (2026-07-28) — the one
   loud control on that page: guide teaches, dojo checks. Lives in section 74
   with the rest of the dojo styles. */
.guide-checkpoint { margin: var(--space-6) 0 var(--space-5); border-color: var(--border-accent); }
.guide-checkpoint .kicker { display: block; margin-bottom: var(--space-2); }
.guide-checkpoint-line { margin: 0 0 var(--space-4); color: var(--text-secondary); line-height: 1.55; max-width: 62ch; }

/* 75. Role menu (admin users) ------------------------------------------------- */
/* The role tag doubles as a quiet details-popout for Tester <-> Client flips.
   Looks like the plain .tag until opened (the feature is deliberately
   unprominent); the panel explains the data consequences before the button. */
.role-menu { position: relative; display: inline-block; }
.role-menu > summary { list-style: none; cursor: pointer; }
.role-menu > summary::-webkit-details-marker { display: none; }
.role-menu > summary:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.role-menu[open] > summary.tag { border-color: var(--border-strong); }
.role-menu-panel {
  position: absolute; left: 0; top: calc(100% + 4px); z-index: 30;
  width: 280px; text-align: left;
  background: var(--bg-elevated); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: var(--space-3);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.45);
}
.role-menu-hint { font-size: 0.8rem; line-height: 1.45; color: var(--text-secondary); margin-bottom: var(--space-3); }

/* 76. Help / support form ------------------------------------------------------ */
/* Form card + a quiet aside of faster routes (Discord, guides, plain email). */
.help-grid { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(240px, 1fr); gap: var(--space-5); align-items: start; }
.help-aside .kicker { display: block; margin-bottom: var(--space-3); }
.help-alt { font-size: 0.88rem; color: var(--text-secondary); margin: 0 0 var(--space-3); }
.help-alt:last-child { margin-bottom: 0; }
@media (max-width: 900px) { .help-grid { grid-template-columns: 1fr; } }

/* 77. Public tester card -------------------------------------------------------- */
/* The opt-in shareable stats page (/card/<token>) — the one unauthenticated
   portal page, so it reads as a broadcast player card: centered, accent-framed,
   chunky mono numerals. The foot CTA is the recruiting hook. */
.tcard-shell { display: flex; justify-content: center; padding: var(--space-8) var(--space-4); }
.tcard {
  width: 100%; max-width: 440px; text-align: center;
  background: var(--bg-surface); border: 1px solid var(--border-accent);
  border-radius: var(--radius-lg); padding: var(--space-8) var(--space-6) var(--space-6);
}
/* Brand row: robot + wordmark, flex-centered (an inline img drifted off-center
   on real phones — owner screenshot 2026-08-04). */
.tcard-brand { display: flex; align-items: center; justify-content: center; gap: var(--space-3); margin-bottom: var(--space-4); }
.tcard-mark { height: 44px; width: auto; }
.tcard-wordmark { height: 15px; width: auto; opacity: 0.95; }
.tcard-kicker { display: block; margin-bottom: var(--space-2); }
.tcard-name {
  margin: 0; font-family: var(--font-logo); font-size: 2.1rem; font-weight: 800;
  letter-spacing: -0.01em; overflow-wrap: anywhere;
}
.tcard-tier {
  margin: var(--space-2) 0 0; font-family: var(--font-mono); font-size: 0.8rem;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--text-secondary);
}
.tcard-unranked { color: var(--text-muted); }
.tcard-stats {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-4) var(--space-2);
  margin: var(--space-6) 0; padding-top: var(--space-5);
  border-top: 1px solid var(--border-default);
}
.tcard-stat { display: flex; flex-direction: column; gap: 2px; }
.tcard-num { font-size: 1.7rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.tcard-lbl {
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted);
}
.tcard-foot { border-top: 1px solid var(--border-default); padding-top: var(--space-5); }
.tcard-pitch { margin: 0 0 var(--space-4); color: var(--text-secondary); font-size: 0.9rem; }
/* Settings: the live card link under the toggle — appears in place (no
   reload), with a copy button and an inline status line. */
.card-link-row { margin: var(--space-3) 0 0; font-size: 0.8rem; overflow-wrap: anywhere; }
.card-link-row { display: flex; align-items: center; gap: var(--space-3); }
.card-link-row a { min-width: 0; overflow-wrap: anywhere; }
.card-link-row .btn { flex-shrink: 0; white-space: nowrap; }
.card-status { margin: var(--space-3) 0 0; font-size: 0.85rem; color: var(--text-secondary); }

/* 78. Robot empty scenes (partials/empty-scene.ejs) --------------------------- */
/* Line-art robot vignettes replacing bare "nothing here" boxes: radar (nothing
   found yet), clipboard+check (queue clear), sleeping robot (nothing active),
   closed crate (no payouts), quiet signal panel (no activity). Neutral
   currentColor strokes; ONE semantic accent per scene via es-accent-* on the
   wrapper (cyan = a CTA exists, magenta = waiting/live, green = money/done).
   Motion: the svg rises in once (these pages' one orchestrated moment), the
   radar sweep turns once and freezes, the eyes ride the idle-robot blink
   allowance. Everything dies under the global section-31 reduced-motion kill.
   One robot per page still applies — see the partial's header comment.
   2026-09-16 (owner show-first round, variant C): the robot and the prop now
   stand on ONE ground line (.es-ground) inside a box cropped to the art, at
   240px, in --text-secondary at full opacity with a 2px stroke, the title in
   --text-primary — before, two small muted icons floated a third of the box
   above a muted title and read as clutter. */
.empty-scene { text-align: center; padding: var(--space-8) var(--space-5); color: var(--text-muted); }
.empty-scene svg {
  display: block; width: 240px; height: auto; margin: 0 auto var(--space-4);
  color: var(--text-secondary);
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
  animation: es-in 0.5s ease-out both;
}
.empty-scene .es-ground { opacity: 0.45; }
@keyframes es-in { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
.empty-scene .es-fill { fill: currentColor; stroke: none; }
.empty-scene .es-zzz { fill: currentColor; stroke: none; font-family: var(--font-mono); font-weight: 700; opacity: 0.7; }
.es-accent-act .es-a { stroke: var(--primary); }
.es-accent-act .es-a.es-fill { fill: var(--primary); stroke: none; }
.es-accent-live .es-a { stroke: var(--magenta); }
.es-accent-live .es-a.es-fill { fill: var(--magenta); stroke: none; }
.es-accent-money .es-a { stroke: var(--success); }
.es-accent-money .es-a.es-fill { fill: var(--success); stroke: none; }
/* Idle blink — same allowance (and keyframes) as the living robot's. */
.empty-scene .es-eye { transform-box: fill-box; transform-origin: center; animation: rb-blink 5.6s linear infinite; }
/* The radar turns ONCE on load, then holds — a scan happened, it found nothing. */
.empty-scene .es-sweep { transform-origin: 162px 45px; animation: es-sweep-once 1.1s ease-out; }
@keyframes es-sweep-once { from { transform: rotate(-330deg); } to { transform: rotate(0deg); } }
/* Bare mode: prop only (viewBox cropped in the partial) — for pages whose
   command header already renders the living robot. */
.empty-scene.es-bare svg { width: 120px; }
.empty-scene .es-text { max-width: 46ch; margin: 0 auto; font-size: 0.9rem; color: var(--text-primary); }
.empty-scene .es-sub { max-width: 46ch; margin: var(--space-2) auto 0; font-size: 0.8rem; color: var(--text-muted); opacity: 0.8; }
.empty-scene .btn { margin-top: var(--space-4); }

/* 79. Command headers (admin + developer dashboards) -------------------------- */
/* The role dashboards' branded band (partials/command-header.ejs): copy left,
   sparse tactical glyphs + the living robot on the right third. Same one-shot
   draw-in language as the tester hero (section 73) — each dashboard's single
   orchestrated moment; nothing loops but the robot's idle blink. Neutral
   hairline frame on purpose: the accent frame is the tester hero's signature.
   The magenta urgent chip is the live voice — the one queue needing eyes. */
.cmd-head {
  position: relative; overflow: hidden;
  background: var(--bg-card); border: 1px solid var(--border-default);
  border-radius: var(--radius-lg);
  /* --space-7 does not exist (scale jumps 6 to 8) — an undefined var()
     silently collapses the declaration. */
  padding: var(--space-6) var(--space-8); margin-bottom: var(--space-6);
}
.cmd-head > :not(.cmd-grid) { position: relative; z-index: 1; }
.cmd-grid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; fill: none; stroke-width: 1.2;
}
.cmd-grid .cg-line { stroke: var(--border-default); opacity: 0.45; }
.cmd-grid .cg-glyph { stroke: var(--border-default); }
.cmd-grid line, .cmd-grid path, .cmd-grid rect, .cmd-grid circle {
  stroke-dasharray: 300; stroke-dashoffset: 300;
  animation: hero-draw 0.9s ease-out forwards;
}
.cmd-grid .cg-2 { animation-delay: 0.15s; }
.cmd-grid .cg-3 { animation-delay: 0.3s; }
.cmd-head .cmd-robot {
  position: absolute; right: var(--space-8); top: 50%;
  width: 104px; height: auto; transform: translateY(-50%);
}
.cmd-copy .kicker { display: block; margin-bottom: var(--space-2); }
.cmd-title { font-size: 1.6rem; margin: 0 0 var(--space-1); }
.cmd-sub { color: var(--text-secondary); margin: 0; font-size: 0.92rem; max-width: 60ch; }
.cmd-urgent {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-3); font-family: var(--font-mono);
  font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
}
.cmd-count { color: var(--magenta); }
.cmd-count.is-money { color: var(--success); }
.cmd-urgent.is-static { color: var(--text-secondary); }
@media (max-width: 900px) { .cmd-grid, .cmd-head .cmd-robot { display: none; } }

/* 80. Signal windows (test/project cards) ------------------------------------- */
/* A card's miniature pipeline (partials/sig-window.ejs), generated from REAL
   counts: one cell per stage, fill = count / the funnel's entry stage.
   Neutral fills strengthen as work progresses; the admin PAID stage is the
   one green (money voice) — developers never get a money stage. Replaced the
   stats row + response-rate bar on both cards; margin-top:auto inherits the
   settled "card footers line up across a row" job from .project-stats. */
.sig-win {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: var(--space-2); margin-top: auto; padding-top: var(--space-2);
}
.sig-cell { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.sig-track {
  display: block; height: 4px; border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.06); overflow: hidden;
}
.sig-fill { display: block; height: 100%; border-radius: inherit; background: rgba(255, 255, 255, 0.18); }
.sig-fill.sig-l1 { background: rgba(255, 255, 255, 0.3); }
.sig-fill.sig-l2 { background: rgba(255, 255, 255, 0.44); }
.sig-fill.sig-l3 { background: rgba(255, 255, 255, 0.44); }
.sig-cell.is-money .sig-fill { background: var(--success); }
.sig-lbl {
  font-family: var(--font-mono); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.06em; color: var(--text-muted); text-transform: uppercase;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sig-lbl b { color: var(--text-secondary); font-weight: 700; }

/* 81. Mission Control scene (tester hero) ------------------------------------- */
/* Hero-actor round (owner call 2026-08-07): the robot lives INSIDE the scene
   svg (nested robot-mark → exact registration with the drawn props at every
   width) and every state is the robot DOING something: holding the invite's
   mission card in its brackets, holding the accepted LOADOUT clipboard (one
   approving nod), operating the standing-by radar console, stamping the
   submitted report COMPLETE itself. The entrance stays ONE ~1.2s beat; after
   it each state keeps exactly ONE quiet perpetual element (law-4 amendment
   2026-08-07): breathing brackets / pulsing here-node / turning radar sweep /
   humming antenna bulb. Every loop's 0%/100% keyframes ARE its rest state,
   so the §31 kill (0.01ms, 1 iteration) lands on a calm finished scene.
   State classes on the svg: mc-s-invite / mc-s-accepted / mc-s-radar /
   mc-s-complete. Voices: magenta = SIGNAL ACQUIRED, green = the reward line
   + the COMPLETE stamp, cyan = the CTA ring only. */
.mission-hero > .mc-stage {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  /* The stage owns the scene crop (moved off .mission-hero 2026-08-13 — the
     hero's overflow:hidden clipped the decline popout). Radius inherited so
     the scene still clips inside the card's rounded corners. */
  overflow: hidden; border-radius: inherit;
}
.mc-scene {
  position: absolute; inset: 0; width: 100%; height: 100%;
  fill: none; stroke-width: 1.2;
}
.mc-scene text { stroke: none; font-family: var(--font-mono); font-weight: 700; }
.mc-scene .mcb { stroke: var(--border-default); opacity: 0.45; }
.mc-scene .mcg { stroke: var(--border-default); }
.mc-scene .mc-dim { opacity: 0.55; }
/* Entrance beat 1: background draws in. */
.mc-bg line, .mc-bg path, .mc-bg circle {
  stroke-dasharray: 300; stroke-dashoffset: 300;
  animation: hero-draw 0.7s ease-out forwards;
}
.mc-bg .mc-2 { animation-delay: 0.12s; }
.mc-bg .mc-3 { animation-delay: 0.24s; }
/* Beat 2 (invite): one scan line crosses the scene (neutral — the motion is
   the meaning; ends invisible, so reduced motion never shows it). */
.mc-scanline { stroke: var(--border-strong); opacity: 0; animation: mc-scan 0.5s ease-in-out 0.25s; }
@keyframes mc-scan {
  from { opacity: 0.9; transform: translateX(0); }
  to { opacity: 0; transform: translateX(250px); }
}
/* The actor: robot + whatever it HOLDS drift together (the idle-robot float
   allowance — props stay in its hand). The radar robot sits at its console
   (no .mc-actor wrapper there), so it does not float. */
/* Periods harmonised 2026-09-02: float 5.6s = the blink period, the state loops
   at 5.6s or 2.8s — one shared beat, not three unrelated rhythms in one frame. */
.mc-actor { animation: mc-float 5.6s ease-in-out infinite; }
@keyframes mc-float { 50% { transform: translateY(-4px); } }
/* Line-art limbs, in the scene's stroke voice. */
.mc-arm { stroke: var(--text-secondary); stroke-width: 1.6; stroke-linecap: round; }
.mc-hand { fill: var(--bg-elevated); stroke: var(--text-secondary); stroke-width: 1.6; }
/* Beat 3 (invite): the mission card pops up into the robot's hand. */
.mc-card { opacity: 0; animation: mc-card-in 0.4s ease-out 0.45s forwards; }
@keyframes mc-card-in {
  from { opacity: 0; transform: translate(20px, 10px); }
  to { opacity: 1; transform: none; }
}
.mc-card .mc-reward { stroke: var(--success); stroke-width: 2; }
/* The brackets close around the held card, then BREATHE — this state's one
   quiet loop (a live lock-on, not a still frame). */
.mc-brackets .mcbr { stroke: var(--text-secondary); stroke-width: 1.6; }
.mc-brackets {
  opacity: 0; transform-origin: 522px 128px;
  animation: mc-brackets-in 0.35s ease-out 0.6s forwards,
             mc-breathe 5.6s ease-in-out 1.4s infinite;
}
@keyframes mc-brackets-in {
  from { opacity: 0; transform: scale(1.14); }
  to { opacity: 1; transform: scale(1); }
}
@keyframes mc-breathe { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
/* Beat 4: the live marker — magenta, because a fresh invite IS live. */
.mc-signal {
  fill: var(--magenta); font-size: 9.5px; letter-spacing: 0.14em;
  opacity: 0; animation: mc-fade-in 0.3s ease-out 0.8s forwards;
}
@keyframes mc-fade-in { to { opacity: 1; } }
/* The robot glances at the card in its hand (to its left), then back — one
   shot, riding the same beat as the section-73 grin. */
.mission-hero.is-invite .rb-gaze { animation: mc-glance 1.3s ease-in-out 0.8s; }
@keyframes mc-glance {
  0%, 100% { transform: translateX(0); }
  30%, 70% { transform: translateX(-7px); }
}
/* Beat 6: the CTA "activates" — one short cyan ring. Purely visual: the
   button is live from first paint. */
.mission-hero.is-invite .mission-actions .btn-primary { animation: mc-cta-ring 0.5s ease-out 1.05s; }
@keyframes mc-cta-ring {
  0% { box-shadow: 0 0 0 0 rgba(46, 230, 255, 0.45); }
  100% { box-shadow: 0 0 0 7px rgba(46, 230, 255, 0); }
}
/* Accepted: the LOADOUT clipboard rises in the robot's hand and it gives ONE
   approving nod (a check, not a celebration); the ACCEPTED you-are-here node
   pulses — this state's quiet loop. */
.mc-loadout, .mc-progress { opacity: 0; animation: mc-rise 0.45s ease-out 0.45s forwards; }
.mc-progress { animation-delay: 0.65s; }
@keyframes mc-rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}
.mc-s-accepted .mc-head { transform-origin: 650px 100px; animation: mc-nod 0.8s ease-in-out 0.9s; }
@keyframes mc-nod {
  0%, 100% { transform: rotate(0deg); }
  35%, 65% { transform: rotate(5deg); }
}
.mc-lo-k { fill: var(--text-muted); font-size: 8px; letter-spacing: 0.14em; }
.mc-lo-v { fill: var(--text-secondary); font-size: 11px; letter-spacing: 0.06em; }
.mc-pline {
  stroke: var(--border-strong);
  stroke-dasharray: 120; stroke-dashoffset: 120;
  animation: hero-draw 0.4s ease-out 0.75s forwards;
}
.mc-node { fill: none; stroke: var(--text-muted); stroke-width: 1.4; }
.mc-node.is-on { fill: var(--text-secondary); stroke: none; }
.mc-here { transform-box: fill-box; transform-origin: center; animation: mc-here 2.8s ease-in-out 1.4s infinite; }
@keyframes mc-here { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.35); opacity: 0.7; } }
.mc-pl { fill: var(--text-muted); font-size: 7.5px; letter-spacing: 0.1em; }
.mc-pl-r { text-anchor: end; }
/* Standing-by: the robot operates its radar console. The sweep turns
   PERPETUALLY — this state's quiet loop (law-4 amendment 2026-08-07; it used
   to sweep once and freeze) — and the two blips brighten as it passes. */
.mc-console { opacity: 0; animation: mc-rise 0.4s ease-out 0.2s forwards; }
.mc-sweep { stroke: var(--border-strong); }
.mc-s-radar .mc-sweep { transform-origin: 528px 118px; animation: mc-sweep-spin 7s linear infinite; }
@keyframes mc-sweep-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.mc-blip { fill: var(--text-secondary); stroke: none; opacity: 0.35; }
.mc-b1 { animation: mc-blip 7s linear 1.2s infinite; }
.mc-b2 { animation: mc-blip 7s linear 4.7s infinite; }
@keyframes mc-blip { 0%, 100% { opacity: 0.35; } 6% { opacity: 0.9; } 18% { opacity: 0.35; } }
/* Away = the console powers down: dim, sweep parked, blips dark (invites are
   paused; the station must not claim to be scanning — honesty rule). */
.mission-hero.is-drowsy .mc-console { opacity: 0.35; animation: none; }
.mission-hero.is-drowsy .mc-sweep { animation: none; opacity: 0; }
.mission-hero.is-drowsy .mc-blip { animation: none; opacity: 0.15; }
/* Mission complete: the robot HOLDS the stamped report (same arm grammar as
   the invite card — a long stamp-tool reach read as a floating line, owner
   catch 2026-08-07); the green imprint pops on the card in its hand, the
   evidence blocks lock in staggered below. No confetti — the stamp and the
   section-73 proud smile ARE the celebration. */
.mc-report { opacity: 0; animation: mc-rise 0.4s ease-out 0.2s forwards; }
.mc-stamp rect { stroke: var(--success); stroke-width: 1.6; }
.mc-stamp text { fill: var(--success); font-size: 10px; letter-spacing: 0.14em; text-anchor: middle; }
.mc-stamp {
  opacity: 0; transform-origin: 522px 144px;
  animation: mc-stamp-in 0.3s ease-out 0.68s forwards;
}
@keyframes mc-stamp-in {
  from { opacity: 0; transform: scale(1.3); }
  to { opacity: 1; transform: scale(1); }
}
.mc-evid rect { stroke: var(--border-strong); }
.mc-evid text { fill: var(--text-muted); font-size: 7.5px; letter-spacing: 0.08em; text-anchor: middle; }
.mc-ev { opacity: 0; animation: mc-rise 0.3s ease-out forwards; }
.mc-ev:nth-child(1) { animation-delay: 0.82s; }
.mc-ev:nth-child(2) { animation-delay: 0.94s; }
.mc-ev:nth-child(3) { animation-delay: 1.06s; }
/* The proud robot's quiet loop: the antenna bulb hums, softly. */
.mission-hero.is-proud .rb-bulb { animation: mc-bulb 2.8s ease-in-out 1.2s infinite; }
@keyframes mc-bulb { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
/* Between 720 and 1000px the text column needs the room: the robot (and its
   arm) step out; the held card / console stay as backdrop texture. */
@media (max-width: 1000px) {
  .mc-scene .mc-bot { display: none; }
}
/* Phones (<720px): the stage collapses to a compact strip above the copy —
   the SAME svg, viewBox swapped to the state's card+robot region by
   pages/dashboard.js. Default is hidden, JS opts in — no-JS phones keep the
   text-first hero. Background + signal text don't survive the scale; hide.
   The robot IS part of the compact crop, so it returns here. */
@media (max-width: 720px) {
  /* The empty hero is display:flex; as a flex ITEM the compact stage would
     shrink-wrap its absolutely-positioned child to 0 width and vanish —
     stack the hero instead (strip above, copy below). Caught 2026-08-07. */
  .mission-hero.is-empty { display: block; }
  .mission-hero > .mc-stage { display: none; }
  .mission-hero > .mc-stage.mc-compact {
    display: block; position: relative; inset: auto; height: 84px;
    margin-bottom: var(--space-3);
  }
  .mc-compact .mc-scene { position: absolute; left: 0; top: 0; height: 100%; width: auto; max-width: 100%; }
  .mc-compact .mc-bg, .mc-compact .mc-scanline, .mc-compact .mc-signal { display: none; }
  .mc-compact .mc-scene .mc-bot { display: inline; }
}

/* 82. Payout batches (payments hub) ------------------------------------------- */
/* One strip per OPEN batch (a downloaded Tremendous CSV): identity + totals
   left, settle (with optional provider order id) / void right. */
.batch-strip {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-3) var(--space-5); margin-bottom: var(--space-3);
}
.batch-info { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }
.batch-id { color: var(--text-muted); font-weight: 700; }
.batch-meta { font-size: 0.88rem; color: var(--text-secondary); }
.batch-actions { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
/* The settle FORM holds two controls (order-id input + button); as a plain
   inline-form it stacked them vertically inside the flex row (owner
   screenshot 2026-08-15). Make the form itself the row. */
.batch-actions .inline-form { display: flex; align-items: center; gap: var(--space-2); }
.batch-ref {
  width: 220px; padding: 6px 10px; font-size: 0.8rem; font-family: var(--font-mono);
  background: var(--bg-elevated); border: 1px solid var(--border-default);
  border-radius: var(--radius-sm); color: var(--text-primary);
}

/* 83. Robot avatars (partials/avatar + robot-defs) ---------------------------- */
/* The tester's profile picture: the company robot wearing achievement-unlocked
   accessories (src/lib/avatar.js decides, robot-defs draws). One static tile
   partial, sized by whoever hosts it through --rb-size; the living robot in
   the hero is still partials/robot-mark, now built from the same parts. */
.rb-avatar { display: inline-block; width: var(--rb-size, 40px); height: var(--rb-size, 40px); line-height: 0; flex-shrink: 0; vertical-align: middle; }
.rb-avatar svg { display: block; width: 100%; height: 100%; }
/* Topnav: no tile around the robot (owner call 2026-08-16 — the box gave an
   initial letter a body; the robot has its own silhouette). 38px mirrors the
   34px brand mark on the other end of the bar. */
.topnav-av.is-robot { --rb-size: 38px; width: 38px; height: 38px; background: none; border: 0; border-radius: 0; }
/* Leaderboard rows: a bare 26px robot between rank and name (the row grid in
   its own section carries the extra column). */
.lb-av { --rb-size: 26px; }
/* Public tester card: the avatar IS the brand mark on the card (glow via .glow-1). */
.tcard-av { --rb-size: 72px; }
/* Settings identity header (testers): the robot inside the 56px circle. */
.identity-avatar.is-robot { --rb-size: 46px; }
/* Settings picker (round 2): a big preview robot beside three slot selects. */
.avatar-pick { display: flex; align-items: flex-start; gap: var(--space-6); flex-wrap: wrap; }
.avatar-pick-preview { --rb-size: 132px; flex-shrink: 0; padding: var(--space-2); }
.avatar-pick-form { flex: 1; min-width: 260px; }
/* Picker round 2b (owner review): radio CHIPS per slot instead of selects —
   None + every accessory; locked ones inert + greyed with the achievement
   that opens them (the wish list). The .chip base is section 12. */
.avatar-pick-slot { border: 0; padding: 0; margin: 0 0 var(--space-4); min-width: 0; }
.avatar-pick-slot legend { padding: 0; margin-bottom: var(--space-2); }
.avatar-pick .chip.is-locked { cursor: not-allowed; color: var(--text-muted); border-style: dashed; background: transparent; }
.avatar-pick .chip.is-locked:hover { border-color: var(--border-default); color: var(--text-muted); }
.avatar-pick .chip.is-locked .icon { width: 13px; height: 13px; }
.avatar-pick .chip-sub { font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); }
.avatar-pick .chip-sub::before { content: '·'; margin: 0 var(--space-1) 0 0; }

/* 84. Marketing-consent group (Settings notifications card) ------------------- */
/* The optional marketing consent sits inside the notifications form,
   separated by the "Optional, and entirely your choice." kicker and spacing
   alone (owner call 2026-08-18 — the earlier hairline frame read as clutter;
   the lawyer's rule only asks for visual separation). */
.settings-consent-group { margin-top: var(--space-5); padding-top: var(--space-4); border-top: 1px solid var(--border-default); }
.settings-consent-group .kicker-muted { display: block; font-size: 0.68rem; }
.settings-consent-group .switch-row { border-bottom: none; padding-bottom: 0; }

/* 85. Recording consent (per-test flags, accept page, form block) ------------- */
/* Lawyer's manual P2 step 13: a recording-flagged test's Accept detours via
   the accept page, where each requested modality is a visibly-equal, unfilled
   Yes/No; the answers stay editable on the feedback form until submit. */
.rec-flag { display: flex; align-items: flex-start; gap: var(--space-3); margin-bottom: var(--space-3); cursor: pointer; }
.rec-flag input { margin-top: 3px; accent-color: var(--primary); }
.rec-q { border: none; margin: 0 0 var(--space-4); padding: 0; }
.rec-q legend { padding: 0; margin-bottom: var(--space-2); color: var(--text-primary); font-weight: 500; }
.rec-choices { display: flex; gap: var(--space-3); }
/* Yes and No are the SAME control — neither is the "encouraged" answer. */
.rec-choice { display: flex; align-items: center; gap: 8px; padding: 8px 18px; border: 1px solid var(--border-strong); border-radius: var(--radius-sm); cursor: pointer; font-family: var(--font-mono); font-size: 0.85rem; color: var(--text-secondary); }
.rec-choice:hover { border-color: var(--border-accent); }
.rec-choice input { accent-color: var(--primary); }
.rec-choice:has(input:checked) { border-color: var(--border-accent); color: var(--text-primary); background: var(--primary-bg); }
.rec-choice input:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.accept-meta { margin: var(--space-2) 0 var(--space-4); color: var(--text-secondary); }
.accept-brief { margin-bottom: var(--space-5); }
.accept-intro-p { max-width: 68ch; }
.accept-actions { display: flex; gap: var(--space-3); align-items: center; margin-top: var(--space-4); }
.rec-panel { margin-bottom: var(--space-5); }
.rec-panel .kicker-muted { display: block; margin-bottom: var(--space-3); }
.rec-update-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }
.rec-update-foot .form-hint { margin: 0; }
/* Dispatch-log consent note (admin) — same muted mono voice as the date. */
.log-rec { color: var(--text-muted); font-size: 0.75rem; white-space: nowrap; }

/* 86. Click-to-reveal disclosures + quiet statuses (2026-08-20 tester report) -- */
/* Hover-only info moved one CLICK behind: button[data-reveal] keeps its old
   span look (UA button chrome reset), the revealed line is muted mono.
   ⚠️ Reset ONLY what the components don't own: this selector (0-1-1) beats
   the component classes (0-1-0), so a `font:`/`margin:` shorthand here wiped
   .qflag's mono face and .match-cell's margin-left:auto on QA (owner
   report 2026-08-22). The global `button { font-family: inherit }` plus each
   component's own rule carry the rest. */
button.match-cell, button.qflag, button.ss-trend {
  background: none; border: none; padding: 0;
  color: inherit; text-align: inherit; cursor: pointer;
}
button.qflag { color: var(--warning); }
/* The dispatch row may wrap: the revealed factor line takes a full second
   line under the row (inert while nothing is revealed). */
.tester-pick label { flex-wrap: wrap; }
.match-reasons { flex-basis: 100%; font-size: 0.72rem; color: var(--text-muted); text-align: right; }
.qflag-reasons { display: block; font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-muted); margin-top: 2px; max-width: 240px; white-space: normal; }
.ss-detail { margin: var(--space-2) 0 0; font-size: 0.74rem; color: var(--text-muted); }
/* Unavailable developer states are quiet TEXT, never a fake button. */
.proj-status { font-family: var(--font-mono); font-size: 0.76rem; color: var(--text-muted); align-self: center; }

/* 87. Settings section navigator (2026-08-20 tester report #8) ---------------- */
/* Jump chips over the card stack, built by pages/settings.js from the
   rendered card titles (role differences stay in the view alone). */
.settings-jump { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-6); }
.settings-jump a {
  padding: 5px 12px; border: 1px solid var(--border-default); border-radius: var(--radius-pill);
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-secondary); text-decoration: none;
}
.settings-jump a:hover { color: var(--primary); border-color: var(--border-accent); }
.card { scroll-margin-top: var(--space-5); }
/* 2026-08-22 (owner call): the navigator is a sticky SIDE rail — the chip row
   scrolled away with the page. The .settings-jump chip rules above are
   retired (no markup emits them any more). Mobile collapses the rail back to
   a wrapping chip row above the cards. */
.settings-layout { display: grid; grid-template-columns: minmax(0, 1fr) 200px; gap: var(--space-6); align-items: start; }
.settings-aside { position: sticky; top: var(--space-5); display: flex; flex-direction: column; gap: 2px; padding-top: var(--space-8); }
.settings-aside a {
  padding: 6px 10px; border-left: 2px solid var(--border-default);
  font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--text-secondary); text-decoration: none;
}
.settings-aside a:hover { color: var(--text-primary); }
.settings-aside a.is-active { color: var(--primary); border-left-color: var(--primary); }
@media (max-width: 900px) {
  /* The aside sits AFTER the cards in the markup (right rail on desktop) —
     flex + order lifts it back above them as a chip row. */
  .settings-layout { display: flex; flex-direction: column; }
  .settings-aside { order: -1; position: static; flex-direction: row; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-4) 0 0; }
  .settings-aside a { border: 1px solid var(--border-default); border-radius: var(--radius-pill); padding: 5px 12px; }
  .settings-aside a.is-active { border-color: var(--border-accent); }
}

/* 89. Back to top (2026-08-22 owner call) -------------------------------------- */
/* Long pages get a fixed chip once the reader is deep (main.js builds it);
   legal pages keep their own "↑ Contents" chip instead. */
.to-top {
  position: fixed; right: var(--space-4); bottom: var(--space-4); z-index: 50;
  padding: var(--space-2) var(--space-3);
  background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  font-family: var(--font-mono); font-size: 0.74rem; color: var(--text-secondary); text-decoration: none;
}
.to-top:hover { color: var(--primary); border-color: var(--border-accent); }

/* 88. Admin "Needs attention" queues (2026-08-22 design review) --------------- */
/* The admin homepage answers "what needs attention now?" — three queue cards
   (evaluate / pay / ending soon); rows are the next items, empty = quiet. */
.queue-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--space-4); }
.queue-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-3); }
.queue-title { font-family: var(--font-logo); font-weight: 600; font-size: 1.05rem; color: var(--text-primary); }
.queue-link { font-family: var(--font-mono); font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-secondary); text-decoration: none; white-space: nowrap; }
.queue-link:hover { color: var(--primary); }
.queue-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  padding: var(--space-2) 0; border-top: 1px solid var(--border-default);
  color: var(--text-secondary); text-decoration: none; font-size: 0.85rem;
}
.queue-row:hover .queue-row-main { color: var(--text-primary); }
.queue-row-main { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.queue-row-when { flex-shrink: 0; font-size: 0.75rem; color: var(--text-muted); }
.queue-clear { margin: 0; color: var(--text-muted); font-size: 0.85rem; }
.queue-line { margin: 0 0 var(--space-2); font-size: 0.85rem; color: var(--text-secondary); }
.queue-more { margin: var(--space-2) 0 0; font-size: 0.72rem; color: var(--text-muted); }

/* 90. Density pass (2026-08-22 pilot: admin dashboard + settings) -------------- */
/* The "AI feel" diagnosis (tester feedback): over-consistent composition — a
   magenta kicker on every box, everything bordered, every number modest.
   Rules piloted here, to graduate into MASTER.md if the owner approves:
   ONE magenta kicker per screen (the screen's live moment); borders belong
   to OBJECTS — groups and preferences sit on the page ground under
   hairlines; on a scoreboard the NUMBERS are the loud thing. */
/* 2026-08-24 (designer finding #5): 0.72rem muted at 0.14em tracking sat at
   ~3.3:1 contrast — below AA at that size, and theatrical at this frequency.
   Slightly larger, secondary ink, calmer tracking; still the quiet voice. */
.sec-label { display: block; font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); }
/* Bare KPIs: the numeral IS the tile. */
.kpi-bare > .stat-card { background: none; border: none; border-radius: 0; padding: 0 0 0 var(--space-4); border-left: 2px solid var(--border-strong); }
.kpi-bare > .stat-card:hover { transform: none; box-shadow: none; border-color: transparent; border-left-color: var(--border-accent); }
.kpi-bare > .stat-card .stat-icon { display: none; }
.kpi-bare > .stat-card .stat-value { font-size: 3.4rem; margin-top: var(--space-1); }
/* Queues: three columns on the page ground, each opened by a strong rule. */
.queue-col { border-top: 2px solid var(--border-strong); padding-top: var(--space-3); min-width: 0; }
/* Settings: preference groups read as document sections, not stacked boxes.
   The danger zone keeps its red frame — isolation is the point there. */
.settings-main .card:not(.danger-zone) { background: none; border: none; border-radius: 0; box-shadow: none; }
.settings-main .card:not(.danger-zone) > .card-header { padding: 0 0 var(--space-3); }
.settings-main .card:not(.danger-zone) > .card-body { padding: var(--space-4) 0 0; }
.settings-identity-band { padding-bottom: var(--space-5); border-bottom: 1px solid var(--border-default); }
/* Profile (density rollout 2026-08-24): the avatar picker, the seven section
   cards and the completion aside are preference GROUPS, not objects — they sit
   on the page ground; each section opens under its own hairline. The section
   icon keeps its glyph but loses the boxed tile. */
#robot-avatar.card, .profile-main > .card, .profile-aside > .card { background: none; border: none; border-radius: 0; box-shadow: none; }
#robot-avatar > .card-header { padding: 0 0 var(--space-3); }
#robot-avatar > .card-body { padding: var(--space-4) 0 0; }
.profile-main { gap: 0; }
details.profile-section { border-top: 1px solid var(--border-default); }
details.profile-section > summary.section-head { padding: var(--space-5) 0; }
details.profile-section > .card-body { padding: var(--space-4) 0 var(--space-6); }
details.profile-section[open] > summary.section-head { border-bottom: none; padding-bottom: 0; }
.profile-section .section-ico { width: auto; height: auto; background: none; border: none; border-radius: 0; }
.profile-aside > .card > .card-body { padding: 0 0 0 var(--space-5); border-left: 1px solid var(--border-default); }
@media (max-width: 900px) { .profile-aside > .card > .card-body { padding-left: 0; border-left: none; border-top: 1px solid var(--border-default); padding-top: var(--space-5); } }
/* Rewards: the two money figures are the loud thing (rule 3) — bare numerals
   over a left rule; the ledger table below keeps its frame (it is the object). */
.rewards-summary { background: none; border: none; border-radius: 0; overflow: visible; gap: var(--space-6); max-width: none; grid-template-columns: repeat(2, max-content); }
.rs-cell { background: none; padding: 0 var(--space-5) 0 var(--space-4); border-left: 2px solid var(--border-strong); }
.rs-v { font-size: 3.4rem; }
/* Help: // Support stays the screen's one kicker; the form and the routes
   aside are groups, not objects — page ground, aside behind a hairline rail. */
.help-grid > .card, .help-aside > .card { background: none; border: none; border-radius: 0; box-shadow: none; }
.help-grid > .card > .card-body { padding: 0; }
.help-aside > .card > .card-body { padding: 0 0 0 var(--space-5); border-left: 1px solid var(--border-default); }
.help-aside .sec-label { margin-bottom: var(--space-3); }
@media (max-width: 900px) { .help-aside > .card > .card-body { padding-left: 0; border-left: none; border-top: 1px solid var(--border-default); padding-top: var(--space-5); } }
/* Feedback form + my-submission: the brief band, recording panel, question
   list and follow-up notes are page content, not objects — they sit on the
   page ground under hairlines; the sticky action bar keeps its chrome and
   the admin's magenta SectionHeaders carry the structure. */
.test-brief.card, .rec-panel.card, .feedback-form > .card, .sub-card.card { background: none; border: none; border-radius: 0; box-shadow: none; }
.test-brief.card > .card-body, .rec-panel.card > .card-body { padding: 0 0 var(--space-5); border-bottom: 1px solid var(--border-default); }
.feedback-form > .card > .card-body { padding: var(--space-2) 0 0; }
.sub-card.card > .card-body { padding: var(--space-4) 0 0; }
.sub-card.card { border-top: 1px solid var(--border-default); margin-top: var(--space-5); }
.test-brief.card + .sub-card.card { border-top: none; }
/* Guide articles + dojo: the editorial kickers stay (exempt device), but
   how-to steps, callouts, tool lists and drill groups are explanations —
   page ground, each opened by a hairline. Specimen boxes (tier example
   cards, drill option rows) keep their frames: they are the objects. */
.howto.card, .dojo-drill.card, .guide-principle.card, .guide-tips.card, .guide-checkpoint.card, .guide-plain.card {
  background: none; border: none; border-radius: 0; box-shadow: none;
  border-top: 1px solid var(--border-default);
}
.howto.card > .card-body, .dojo-drill.card > .card-body, .guide-principle.card > .card-body,
.guide-tips.card > .card-body, .guide-checkpoint.card > .card-body, .guide-plain.card > .card-body { padding: var(--space-5) 0 var(--space-2); }
/* Admin evaluation: the submission answers and the scoring panel sit on the
   page ground; the aside becomes a left-hairline rail (the profile pattern).
   The .form-section-title hairlined labels become the document structure. */
.profile-grid > .card, .eval-aside > .card { background: none; border: none; border-radius: 0; box-shadow: none; }
.profile-grid > .card > .card-body { padding: 0; }
.eval-aside > .card > .card-body { padding: 0 0 0 var(--space-5); border-left: 1px solid var(--border-default); }
@media (max-width: 900px) { .eval-aside > .card > .card-body { padding-left: 0; border-left: none; border-top: 1px solid var(--border-default); padding-top: var(--space-5); } }
/* Admin logs: the toggle/notify strips are controls, not objects — page
   ground over a hairline; the log tables keep their frames. */
.log-toggle-card.card { background: none; border: none; border-radius: 0; box-shadow: none; }
.log-toggle-card.card > .card-body { padding: 0 0 var(--space-4); border-bottom: 1px solid var(--border-default); }
/* Dispatch board: the step rail is controls on the page ground (its quiet
   sec-labels structure it); the magenta match count stays the screen's one
   live voice. The dispatch log opens under a hairline instead of a frame. */
.dispatch-rail { background: none; border: none; border-radius: 0; padding: 0; }
.dispatch-log.card { background: none; border: none; border-radius: 0; box-shadow: none; border-top: 1px solid var(--border-default); }
.dispatch-log.card > .card-header { padding: var(--space-5) 0 var(--space-2); border-bottom: none; }
.dispatch-log.card > .card-body { padding: 0; }
/* Developer project: the ratings aggregate is an explanation, not an object —
   hairline section on the page ground; each response card keeps its frame. */
.insight-card.card { background: none; border: none; border-radius: 0; box-shadow: none; border-top: 1px solid var(--border-default); margin-bottom: var(--space-5); }
.insight-card.card > .card-header { padding: var(--space-5) 0 var(--space-2); border-bottom: none; }
.insight-card.card > .card-body { padding: 0 0 var(--space-2); }
/* Legal: the document reads on the page ground under a hairline (SHELL-only
   change — the doc templates are content-hashed into consent records and
   stay untouched). The runtime Contents box keeps its frame: navigation. */
.legal-page > .card { background: none; border: none; border-radius: 0; box-shadow: none; border-top: 1px solid var(--border-default); margin-top: var(--space-4); }
.legal-page > .card > .card-body.legal-doc { padding: var(--space-5) 0 0; }

/* 91. De-grid pass (2026-08-24, designer findings #3) -------------------------- */
/* Uniform card grids read as generated. Work items become operational ROWS
   (admin tests + developer projects — partials admin/test-row and
   developer/project-row); the guides index becomes an editorial list. */
.op-rows { display: flex; flex-direction: column; margin-bottom: var(--space-6); }
/* Stopped rows (expired / inactive) read muted through their TEXT, never a
   whole-row opacity: the key art is a child of the row too, so the old
   opacity: 0.75 darkened the backdrop on those rows and not on the live ones
   — "sometimes darker, don't know when" (owner 2026-09-10). Actions stay. */
.op-rows.is-stopped .op-name, .op-rows.is-stopped .op-name a { color: var(--text-secondary); }
.op-rows.is-stopped .op-meta, .op-rows.is-stopped .sig-lbl, .op-rows.is-stopped .sig-lbl b { color: var(--text-muted); }
.op-rows.is-stopped .sig-fill { opacity: 0.6; }
.op-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 300px auto;
  gap: var(--space-5); align-items: center;
  padding: var(--space-4) 0; border-bottom: 1px solid var(--border-default);
}
.op-row:last-child { border-bottom: none; }
.op-name { margin: 0; font-size: 1rem; display: inline-flex; align-items: center; gap: var(--space-2); }
.op-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2) var(--space-3); margin-top: var(--space-1); font-size: 0.8rem; }
.op-due { color: var(--text-muted); font-size: 0.76rem; }
.op-due.is-overdue { color: var(--error); }
.op-dev { font-size: 0.8rem; }
.op-sig .sig-win { margin-top: 0; padding-top: 0; }
.op-act { display: flex; align-items: center; gap: var(--space-2); justify-content: flex-end; flex-shrink: 0; }
.op-row .payout-ready { margin-top: var(--space-2); }
@media (max-width: 900px) {
  .op-row { grid-template-columns: 1fr; gap: var(--space-3); }
  .op-act { justify-content: flex-start; }
}
/* Guides: one featured lead, then grouped text links. */
.guide-featured { border-top: 1px solid var(--border-default); padding: var(--space-5) 0 var(--space-6); }
.guide-featured h2 { margin: var(--space-3) 0 var(--space-2); font-size: 1.35rem; }
.guide-featured h2 a { color: var(--text-primary); }
.guide-featured h2 a:hover { color: var(--primary); text-decoration: none; }
.guide-featured p { margin: 0; color: var(--text-secondary); max-width: 60ch; }
.guide-featured .guide-featured-links { display: flex; gap: var(--space-5); flex-wrap: wrap; margin-top: var(--space-3); }
.guide-featured-links a { font-size: 0.82rem; }
.guide-list { display: flex; flex-direction: column; margin-bottom: var(--space-5); }
/* Rows must FEEL tappable (owner note 2026-08-24): leading glyph, stacked
   title + line, a trailing arrow that answers the hover. */
.guide-item {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-2); margin: 0 calc(-1 * var(--space-2));
  border-bottom: 1px solid var(--border-default);
  border-radius: var(--radius-sm);
  text-decoration: none;
  transition: background 0.15s ease;
}
.guide-item:last-child { border-bottom: none; }
.guide-item:hover { text-decoration: none; background: rgba(255, 255, 255, 0.03); }
.guide-item-ico { display: inline-flex; align-items: center; justify-content: center; width: 22px; flex-shrink: 0; color: var(--text-secondary); }
.guide-item-ico svg { width: 20px; height: 20px; }
.guide-item-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.guide-item-title { color: var(--text-primary); font-weight: 600; font-size: 0.95rem; }
.guide-item:hover .guide-item-title { color: var(--primary); }
.guide-item-desc { color: var(--text-muted); font-size: 0.83rem; line-height: 1.45; }
.guide-item-go { margin-left: auto; flex-shrink: 0; color: var(--text-muted); font-size: 0.85rem; opacity: 0; transform: translateX(-4px); transition: opacity 0.15s ease, transform 0.15s ease, color 0.15s ease; }
.guide-item:hover .guide-item-go { opacity: 1; transform: translateX(0); color: var(--primary); }
@media (prefers-reduced-motion: reduce) { .guide-item, .guide-item-go { transition: none; } .guide-item-go { opacity: 1; transform: none; } }
@media (pointer: coarse) { .guide-item-go { opacity: 1; transform: none; } }
/* Tester dashboard side rail (owner round 2026-08-24): leaderboard + trophy
   case share ONE rail boundary — the two card frames go, a single left
   hairline carries the rail, a hairline separates the sections. Stacked
   ≤900px (the rail goes full-width) it becomes a top rule. */
.dash-side { border-left: 1px solid var(--border-default); padding-left: var(--space-5); }
.leaderboard { background: none; border: none; border-radius: 0; padding: 0; }
.trophy-case { background: none; border: none; border-radius: 0; padding: var(--space-5) 0 0; margin-top: var(--space-5); border-top: 1px solid var(--border-default); }
@media (max-width: 900px) { .dash-side { border-left: none; padding-left: 0; border-top: 1px solid var(--border-default); padding-top: var(--space-5); } }
/* The smallest mono labels on the dashboard read, not decorate (owner
   round): a step up in size, secondary ink where they carry information.
   Earned trophy names step to primary so the unlock stays visible. */
.lb-total { font-size: 0.74rem; color: var(--text-secondary); }
.lb-rated { font-size: 0.74rem; color: var(--text-secondary); }
.lb-tab { font-size: 0.72rem; }
.tc-label { font-size: 0.78rem; color: var(--text-secondary); }
.tc-badge.is-earned .tc-label { color: var(--text-primary); }
.tc-count { color: var(--text-secondary); }
.score-strip { color: var(--text-secondary); }
.mission-log-count { font-size: 0.78rem; color: var(--text-secondary); }
.mission-date { color: var(--text-secondary); }
.mission-group-count { font-size: 0.74rem; }
.pager-status { color: var(--text-secondary); }
/* Score-trend reveal (owner notes 2026-08-25): keyed segments instead of a
   run-on dash chain, and INSIDE the score strip's frame — a full-width flex
   child under an inner hairline, so opening it expands the strip and the
   numbers unmistakably belong to the sparkline. */
.ss-detail { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-5); color: var(--text-secondary); flex-basis: 100%; margin: 0; padding-top: var(--space-3); border-top: 1px solid var(--border-default); }
.ss-detail[hidden] { display: none; }
.ssd-seg { white-space: nowrap; }
/* The 10-score list is the one long segment — it wraps internally (owner
   mobile report 2026-08-25: nowrap pushed it out of the strip at 375px);
   the short tier/streak segments stay unbreakable. */
.ssd-seg.ssd-scores { white-space: normal; min-width: 0; }
.ssd-k { font-weight: 700; text-transform: uppercase; letter-spacing: 0.07em; margin-right: 4px; }
/* Rewards on mobile (owner report 2026-08-25): the max-content summary
   columns forced a horizontal page scroll — the two money figures stack;
   and the ledger drops its Submitted column so the table fits the viewport
   (the date still lives on each my-submission page; .table-wrap scroll
   remains the fallback for very narrow screens). */
@media (max-width: 640px) {
  .rewards-summary { grid-template-columns: 1fr; gap: var(--space-5); }
  .rewards-table th:nth-child(2), .rewards-table td:nth-child(2) { display: none; }
  .table-wrap .rewards-table { min-width: 0; } /* the .table-wrap .table 560px floor forced the sideways scroll */
  .rewards-table .badge { white-space: normal; } /* "Awaiting review" may take two lines instead of the whole row scrolling */
  .rewards-table th, .rewards-table td { padding-left: var(--space-2); padding-right: var(--space-2); }
  /* The strip's wrapped stats looked ragged (owner report) — a two-column
     grid aligns them; the revealed detail line keeps the full width. */
  .score-strip { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); align-items: center; }
  .score-strip .ss-detail { grid-column: 1 / -1; }
}
/* Settings group boundaries (owner call 2026-08-24): the unboxed document
   had lost its edges. The owner chose MAGENTA group headings over the
   two-tier-rules option — a deliberate, Settings-scoped exemption to the
   one-kicker rule, recorded in MASTER.md; not a general license for
   magenta structure labels elsewhere. */
.settings-sec { color: var(--magenta); }
.rec-panel .sec-label { display: block; margin-bottom: var(--space-3); }

/* 92. Review tidy-up (2026-09-02) -------------------------------------------- */
/* The mission-log name is a real <button> now (the brief opens from the
   keyboard, not only on a mouse click). It keeps the old span's look — no
   chrome, inherits the row's type and colour, ellipsis from .mission-name —
   and takes the cyan act voice on hover; the global :focus-visible ring (§31)
   covers focus. */
button.mission-name { background: none; border: 0; padding: 0; margin: 0; font: inherit; font-weight: 600; color: inherit; text-align: left; cursor: pointer; width: 100%; }
button.mission-name:hover { color: var(--primary); }

/* 93. Profile declutter (2026-09-02) ------------------------------------------ */
/* Languages: a searchable picker over the existing checkbox chips — chosen
   chips stay visible, the other ~30 appear only while the filter matches
   (pages/profile.js). Same inputs and names; the server sees nothing new. */
.chip-filter { width: 100%; max-width: 360px; margin-bottom: var(--space-2); }
.chip-group[data-chip-picker] .chip[hidden] { display: none; }
.chip-group[data-chip-picker]:empty { display: none; }
/* Compact avatar card: the robot + what it wears stay visible; the chip rows
   sit one click behind a quiet disclosure. */
.avatar-pick-wearing { margin: var(--space-2) 0 0; color: var(--text-secondary); font-size: 0.92rem; }
.avatar-pick-form .form-more { margin-top: var(--space-3); }
.avatar-pick-form .form-more[open] { margin-bottom: var(--space-2); }

/* 94. Settings declutter (2026-09-02) ----------------------------------------- */
/* Rare/destructive forms sit behind quiet disclosures (change password, the
   delete flow, the card-link mechanics). The danger trigger reads in the
   error voice at body size — it is the one action in the red frame. */
.card .form-more { margin-top: var(--space-3); }
.card .form-more[open] { margin-bottom: var(--space-1); }
.card .form-more form, .card .form-more .form-group:first-of-type { margin-top: var(--space-3); }
.danger-more summary { color: var(--error); font-size: 0.92rem; font-weight: 600; }
.danger-more summary::before { color: var(--error); }
.danger-more[open] summary { margin-bottom: var(--space-2); }

/* 95. Settings hierarchy (2026-09-02) ------------------------------------------ */
/* Owner: "lots of white text, sections not easily visible". Three levels
   now read as three levels — group kicker (magenta, settled) → section
   title (Outfit, larger, on a STRONG rule, more air above) → row title
   (medium weight, body size). Row descriptions stay secondary. */
.settings-main .card:not(.danger-zone) + .card:not(.danger-zone) { margin-top: var(--space-8); }
.settings-main .card:not(.danger-zone) > .card-header { border-bottom: 1px solid var(--border-strong); padding-bottom: var(--space-2); }
.settings-main .card:not(.danger-zone) > .card-header .card-title { font-family: var(--font-logo); font-size: 1.25rem; font-weight: 700; letter-spacing: -0.01em; }
.switch-title { font-weight: 500; font-size: 0.95rem; color: var(--text-primary); }
.settings-main .switch-row { padding: var(--space-3) 0; }
/* The marketing (consent) block drew its own top rule right under the last
   notification row's bottom rule — two lines. The row rule is the divider. */
.settings-consent-group { border-top: none; margin-top: var(--space-4); padding-top: 0; }

/* 96. Identity system (2026-09-02) --------------------------------------------- */
/* Testers are their robot everywhere — admin tables, the dispatch board, the
   developer response cards — the same tile as their topnav / leaderboard /
   card. The letter tile remains only for staff and client accounts, which
   have no robot. The host keeps its footprint; the chrome goes. */
.user-cell-avatar.is-robot, .tester-av.is-robot, .resp-avatar.is-robot { background: none; border: 0; border-radius: 0; }
.user-cell-avatar.is-robot { --rb-size: 30px; }
.tester-av.is-robot { --rb-size: 32px; }
.resp-avatar.is-robot { --rb-size: 38px; }

/* 97. Design-law slips (2026-09-02 design check) ------------------------------- */
/* Dispatch board: candidates were stacked framed boxes inside a framed panel
   (de-grid amendment 8 — one rail, hairline rows). Selection = tint + a cyan
   left rule instead of a second frame. */
.tester-pick { gap: 0; }
.tester-pick label { border: 0; border-bottom: 1px solid var(--border-default); border-radius: 0; padding: var(--space-3) var(--space-2); }
.tester-pick label:last-child { border-bottom: 0; }
.tester-pick label:hover { border-color: var(--border-default); }
.tester-pick label:has(input:checked) { border-color: var(--border-default); background: var(--primary-bg); box-shadow: inset 3px 0 0 var(--primary); }
/* A DISABLED primary button must not read as a teal call to action —
   cyan is the act voice, and there is nothing to act on yet. */
.btn-primary:disabled, .btn-primary[disabled] { background: var(--bg-elevated); color: var(--text-muted); border: 1px solid var(--border-default); opacity: 1; }
/* Row menus inside table cells (users list). */
td .card-menu { display: inline-block; text-align: left; }

/* 98. Review round 2 (2026-09-02) ------------------------------------------- */
/* The Tremendous export became a POST (it opens a payout batch): the three
   trigger sites are forms now, styled exactly as the links they replaced. */
.payout-ready-form, .inline-form { display: inline; margin: 0; }
.op-row .payout-ready-form { display: inline-flex; margin-top: var(--space-2); }
button.payout-ready { background: none; border: 0; padding: 0; cursor: pointer; line-height: inherit; }
button.link-btn {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: inherit; color: var(--primary); text-decoration: none;
}
button.link-btn:hover { text-decoration: underline; }

/* 100. Open calls (2026-09-09) ------------------------------------------------- */
/* The tester board (rows reuse the mission-log grid), the dashboard strip, the
   commit questions' NDA block, and the dispatch board's raised-hand tag +
   pool mix. Quiet by design: one live kicker per screen stays with the hero. */
.calls-strip { display: flex; align-items: center; gap: var(--space-4); padding: var(--space-3) var(--space-4); margin-bottom: var(--space-4);
  border: 1px solid var(--border-strong); border-radius: var(--radius); color: var(--text-primary); text-decoration: none; background: var(--bg-surface); }
.calls-strip:hover { border-color: var(--border-accent); text-decoration: none; }
/* The count reads first (owner call 2026-09-10): a bare tabular numeral beside
   the hand icon (cyan = the strip is an action), the label + sentence stacked
   beside it, the action at the far end. */
.calls-strip-n { display: inline-flex; align-items: center; gap: var(--space-2); flex-shrink: 0; font-size: 1.7rem; font-weight: 600; line-height: 1; color: var(--text-primary); }
.calls-strip-n svg { width: 20px; height: 20px; color: var(--primary); }
.calls-strip-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.calls-strip-text .sec-label { font-size: 0.72rem; }
.calls-strip-line { font-size: 0.92rem; color: var(--text-secondary); }
.calls-strip-go { flex-shrink: 0; color: var(--primary); font-size: 0.78rem; }
.call-row { cursor: default; }
.call-row .mission-name { cursor: pointer; }
.call-state { color: var(--success); font-size: 0.78rem; white-space: nowrap; }
.nda-q .nda-intro { margin-bottom: var(--space-2); }
.nda-accept { margin-top: var(--space-2); }
.nda-done { color: var(--text-secondary); font-size: 0.82rem; margin: 0; }
.nda-form-row { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.nda-form-row input[type="file"] { flex: 1 1 260px; }
.tag-hand { color: var(--primary); border-color: var(--border-accent); flex-shrink: 0; }
.tag-call { color: var(--primary); border-color: var(--border-accent); }
.pool-mix { color: var(--text-secondary); font-size: 0.75rem; }
/* The form builder's quiet section labels had no rhythm since the 2026-09-02
   de-grid pass (0px above and below, the label touched both cards). Same
   spacing as the first, magenta one, minus its colour (owner report 09-09). */
.test-builder > .sec-label { margin: var(--space-6) 0 var(--space-3); }
.test-builder > .sec-label.settings-sec { margin-top: 0; }

/* 101. Key art (2026-09-09) ----------------------------------------------------- */
/* An image per test behind the mission hero, on the rows, in the brief popup
   and on the feedback form. The ground stays black: the art is masked into it
   (an image fade, not a surface gradient — the design law is about surfaces),
   sits under every sibling, and a missing image shows nothing. */
.test-art { position: absolute; display: block; background-size: cover; background-position: center; background-repeat: no-repeat; pointer-events: none; }
/* Parent-qualified so an existing "every hero child is relative" rule cannot
   outrank the layer (it did: the span computed relative and 0×0). */
.mission-hero > .test-art, .page-header > .test-art, .modal > .test-art, .mission-row > .test-art, .op-row > .test-art { position: absolute; z-index: -1; }
/* The host isolates its stacking context, so z-index -1 paints the art above
   the host's own background and below EVERY child, positioned or not — the
   hero's robot scene keeps its own absolute placement untouched. */
.mission-hero { position: relative; overflow: hidden; isolation: isolate; }
.hero-art { inset: 0; opacity: 0.38;
  -webkit-mask-image: linear-gradient(to right, rgba(0,0,0,0.95), rgba(0,0,0,0.3) 45%, transparent 72%);
          mask-image: linear-gradient(to right, rgba(0,0,0,0.95), rgba(0,0,0,0.3) 45%, transparent 72%); }
.banner-art { inset: 0 0 auto 0; height: 132px; opacity: 0.45;
  -webkit-mask-image: linear-gradient(to bottom, rgba(0,0,0,0.9), transparent);
          mask-image: linear-gradient(to bottom, rgba(0,0,0,0.9), transparent); }
/* --space-6, not --space-7: that token does not exist and an undefined var()
   voided the whole padding (the text sat on the border — owner report 09-09). */
.page-header.has-art { position: relative; overflow: hidden; isolation: isolate; padding: var(--space-6) var(--space-5) var(--space-4); border: 1px solid var(--border-default); border-radius: var(--radius-lg); }
.modal.has-art { position: relative; isolation: isolate; }
.modal.has-art .modal-head { padding-top: 96px; }
/* List rows (mission log, admin tests): the art fades IN from the right edge
   under the state cell, the name side stays plain black (owner call 09-09 —
   the 44×20 thumbnail read as a stray stamp). Same masking as the hero. */
/* No overflow clip here: the art is inset 0, so it never leaves the row, and a
   clip swallowed the row's ⋯ popout (owner report 2026-09-10). */
.mission-row, .op-row { position: relative; isolation: isolate; }
/* Each row is its own stacking context (the art sits at z-index -1 inside it),
   so a later sibling paints OVER an earlier row's open ⋯ menu. The row that
   holds an open menu rises above its siblings for as long as it is open
   (external review 2026-09-10). */
.op-row:has(details[open]) { z-index: 2; }
.row-bg { inset: 0; opacity: 0.24;
  -webkit-mask-image: linear-gradient(to left, rgba(0,0,0,0.95), rgba(0,0,0,0.3) 35%, transparent 70%);
          mask-image: linear-gradient(to left, rgba(0,0,0,0.95), rgba(0,0,0,0.3) 35%, transparent 70%); }
.art-row { display: flex; gap: var(--space-5); align-items: flex-start; flex-wrap: wrap; }
.art-preview { width: 230px; height: 108px; flex-shrink: 0; border-radius: var(--radius); background-size: cover; background-position: center; background-color: var(--bg-elevated); border: 1px solid var(--border-default); }
.art-preview.is-empty { border-style: dashed; }
.art-side { flex: 1 1 260px; min-width: 0; }
.art-form-row { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
.art-form-row input[type="file"] { flex: 1 1 220px; }
.tag-hand { display: inline-flex; align-items: center; gap: 4px; }
.tag-hand svg { width: 12px; height: 12px; }
/* The candidate row (owner report 2026-09-09: the evidence and match cells
   wrapped under the name): the identity block claims the free width and the
   tags never break mid-word; the row still wraps rather than hide a name on
   a narrow pane. The shorter icon tag and "64 h played" do the rest. */
.tester-pick .tester-info { flex: 1 1 160px; }
.tester-pick .tester-email, .tester-pick .tester-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tester-pick .tag { white-space: nowrap; }
/* Open calls as CARDS (owner call 2026-09-09: with key art on every test the
   page is a storefront, not a work queue — the rows-not-grids rule is for
   operational lists). The art keeps the Steam header's 460×215 ratio. */
.call-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--space-4); }
.call-card { display: flex; flex-direction: column; background: var(--bg-card); border: 1px solid var(--border-default); border-radius: var(--radius-lg); overflow: hidden; transition: border-color 0.15s ease; }
.call-card:hover { border-color: var(--border-strong); }
.call-card.is-raised { border-color: rgba(67, 217, 143, 0.35); }
.call-card-art { position: relative; display: block; width: 100%; aspect-ratio: 460 / 215; padding: 0; border: none; cursor: pointer; background-color: var(--bg-elevated); background-size: cover; background-position: center; background-repeat: no-repeat; }
.call-card-art.is-empty { padding-bottom: 28px; }
/* The art DISSOLVES into the card (owner ask 2026-09-12: the image ended on a
   visible line): a mask fades its lower part to nothing, so the card surface
   shows through, and the body starts inside the fade — the title sits on the
   image's tail the way the hero title does. An image fade, not a surface
   gradient. The old ::after overlay faded to the page ground (#0A0B0D) on a
   card surface (#101214), which is exactly where the line came from. The
   empty tile gets the same mask, so the two card kinds keep one silhouette. */
.call-card-art { -webkit-mask-image: linear-gradient(to bottom, #000 34%, transparent 92%); mask-image: linear-gradient(to bottom, #000 34%, transparent 92%); }
.call-card-reward { position: absolute; left: var(--space-3); top: var(--space-3); z-index: 1; font-size: 0.85rem; font-weight: 600; padding: 2px 8px; border-radius: var(--radius-sm); background: rgba(10, 11, 13, 0.72); }
.call-card-body { display: flex; flex-direction: column; gap: var(--space-2); padding: 0 var(--space-4) var(--space-4); margin-top: -36px; position: relative; z-index: 1; }
.call-card-name { display: block; width: 100%; padding: 0; border: none; background: none; text-align: left; cursor: pointer; font-family: var(--font-logo); font-weight: 600; font-size: 1.02rem; color: var(--text-primary); }
.call-card-name:hover { color: var(--primary); }
.call-card-meta { font-size: 0.74rem; color: var(--text-muted); display: flex; flex-direction: column; gap: 2px; }
/* The deadline reads first (secondary), the requirements sit beneath it (muted) — review 2026-09-11. */
.call-card-when { color: var(--text-secondary); }
.call-card-foot { display: flex; align-items: center; gap: var(--space-2); margin-top: var(--space-2); flex-wrap: wrap; }
.call-card-foot .call-state { display: inline-flex; align-items: center; gap: var(--space-2); }

/* 99. Robot on the error screens (2026-09-02) --------------------------------- */
/* The living robot replaces the icon badge on 404 / 403 / 500 / suspended:
   same mark as the hero, ONE mood per screen, the code + title stay the
   headline (no speech bubble). It rises in once (the page's one moment),
   then only the idle blink; §31 kills all of it. Owner call 2026-09-02: on
   the screens that ASK the tester something (username pick, terms
   interstitial, accept page) it felt out of place — error pages only. */
.robot-ask { display: block; width: 112px; height: auto; margin: 0 auto var(--space-3); animation: ask-in 0.5s ease-out both; }
@keyframes ask-in { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
/* Moods (the §73 expressions, re-scoped): a page that isn't there = the eyes
   search the room; a server that fell over or a suspended account = lids
   half-closed, eyes still. 403 is the neutral face. */
.robot-ask.is-scan .rb-gaze { animation: rb-scan 7s ease-in-out infinite; }
.robot-ask.is-down .rb-eye { animation: none; transform: scaleY(0.5); }
.robot-ask.is-down .rb-gaze { animation: none; }

/* 102. Open calls polish (2026-09-10, external review of the tester flow) --------- */
/* Cards: the action row sits on the bottom edge of every card whatever the
   text above it (the body grows, the foot is pushed down); a test without
   art wears its letter tile — the identity the portal gives anyone without a
   picture — in the same slot so the grid stays aligned; the registered state
   says what it is and shows the tester's OWN answers under it. The popup
   carries the same door as the card. The commit pages group each explanation
   with its choice: an Outfit title on a rule per block (Settings hierarchy),
   the questions as rows under hairlines, the brief one click behind. */
.call-card-body { flex: 1 1 auto; }
.call-card-foot { margin-top: auto; padding-top: var(--space-2); }
.call-card-art.is-empty { display: flex; align-items: center; justify-content: center; }
.call-card-mono {
  display: inline-flex; align-items: center; justify-content: center;
  width: 64px; height: 64px; border-radius: 50%;
  background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border-default);
  color: var(--text-secondary); font-family: var(--font-logo); font-weight: 700; font-size: 1.4rem; letter-spacing: 0.02em;
}
.call-registered { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
.call-prefs { font-size: 0.72rem; color: var(--text-secondary); }
.modal-call-actions { align-items: center; }
.modal-call-actions .call-state { display: inline-flex; align-items: center; gap: var(--space-2); margin-right: auto; }
.accept-meta-sep { margin: 0 var(--space-2); color: var(--text-muted); }
.brief-more { margin: 0 0 var(--space-5); }
.brief-more summary { cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: var(--space-2); font-family: var(--font-mono); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-secondary); }
.brief-more summary::-webkit-details-marker { display: none; }
.brief-more summary::before { content: '+'; color: var(--text-muted); width: 1em; text-align: center; }
.brief-more[open] summary::before { content: '–'; }
.brief-more summary:hover, .brief-more summary:focus-visible { color: var(--primary); }
.brief-more .test-desc { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--border-default); max-width: 68ch; }
.commit-block + .commit-block { margin-top: var(--space-6); }
.commit-title { font-family: var(--font-logo); font-size: 1.25rem; font-weight: 600; margin: 0 0 var(--space-3); padding-bottom: var(--space-2); border-bottom: 1px solid var(--border-strong); }
.commit-p { margin: 0 0 var(--space-2); max-width: 68ch; font-size: 0.9rem; line-height: 1.55; color: var(--text-secondary); }
.commit-p.is-lead { font-size: 0.95rem; color: var(--text-primary); }
.commit-choices { margin-top: var(--space-4); border-top: 1px solid var(--border-default); }
.commit-choices .rec-q { margin: 0; padding: var(--space-4) 0; border-bottom: 1px solid var(--border-default); }
.commit-choices .nda-accept, .commit-choices .nda-done { margin: 0; padding: var(--space-4) 0; border-bottom: 1px solid var(--border-default); }
.commit-choices .nda-done { display: flex; align-items: center; gap: var(--space-2); }
.commit-next { margin: var(--space-5) 0 0; max-width: 68ch; font-size: 0.9rem; line-height: 1.55; color: var(--text-secondary); }
.commit-block + .accept-actions { margin-top: var(--space-5); }

/* 103. Pool scope (2026-09-15, Filip's methodology: who is asked a question) --- */
/* Builder row: P / M1 / M2 ticks beside the Required select. Mono micro-labels
   that read (>= 0.72rem, secondary ink); a tick the test cannot produce is
   hidden, a forced-off tick (footage on a screen-recording test) is dimmed. */
.q-pools { display: inline-flex; align-items: center; gap: 4px; }
.q-pool { display: inline-flex; align-items: center; gap: 5px; padding: 4px 8px; border: 1px solid var(--border-default); border-radius: var(--radius-sm); font-family: var(--font-mono); font-size: 0.72rem; color: var(--text-secondary); cursor: pointer; }
.q-pool input { accent-color: var(--primary); margin: 0; }
.q-pool:has(input:checked) { border-color: var(--border-strong); color: var(--text-primary); }
.q-pool:has(input:disabled) { opacity: 0.55; cursor: default; }
/* Evaluation page: a question the tester's pool was never asked is a fact, not a blank. */
.answer-notasked .q { color: var(--text-muted); }
.answer-notasked .a { font-family: var(--font-mono); font-size: 0.8rem; }
