/* learning-center — phone-first.
 *
 * TWO THEMES, ONE PALETTE.
 *
 * The tokens below are the LIGHT theme, still worn by the neutral landing /
 * closed pages and by the whole of /admin. The student-facing surfaces — the
 * hub, the walkthrough wizard, the staged entry and the exam workspace — wear
 * the DARK theme: the same tokens, remapped from the Militech-cyan hub palette
 * (`--hub-*`, declared on :root just below), under `body.lc-dark`. That class
 * is emitted by `formPage()` in src/html.ts and by nothing else, so the two
 * themes can never collide on one page.
 *
 * The dark remap lives in ONE block at the very bottom of this file — see
 * "DARK THEME". It has to be last: its element-level rules
 * (`body.lc-dark .scenario-panel`, …) tie on specificity with the staging
 * rules above them, and ties are broken by source order.
 */

:root {
  --bg: #f6f7f9;
  --surface: #ffffff;
  --ink: #1a1d21;
  --muted: #5c636b;
  --line: #dde1e6;
  --accent: #0b6bcb;
  --accent-ink: #ffffff;
  --danger: #c62828;
  --ok: #1b7f3b;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.06), 0 1px 3px rgba(16, 24, 40, 0.1);

  /* Militech-cyan palette (s9-ops/doctrine/BRAND.md). Declared here rather
     than on `#flow-root.hub-mode` because the hub is no longer the only thing
     that wears it: the wizard, the "This one counts" interstitial, the staged
     entry and the whole exam workspace all derive from these. The hub still
     opts IN to the canvas background — see `#flow-root.hub-mode`. */
  --hub-canvas: #06060a;
  --hub-cyan: #00d4ff;
  --hub-cyan-bright: #4aeaff;
  --hub-cyan-dim: #0099cc;
  --hub-gunmetal: #1a1f26;
  --hub-ink: #e8edf1;
  --hub-muted: #7c8894;

  /* Chakra Petch — Section 9 display face. HEADINGS, LABELS AND BADGES ONLY:
     body copy, hints, field text and material text stay in the system sans,
     because these are long-form reading and writing surfaces. */
  --font-display: "Chakra Petch", -apple-system, BlinkMacSystemFont, "Segoe UI",
    Roboto, "Helvetica Neue", Arial, sans-serif;

  /* TYPE SCALE — six steps (2026-07-29, GHOST-365 critique row 11). The file
     used to mint 26 local font sizes, 11 of them between 12.9 and 16px, so
     nothing outranked anything and low-DPI panels rendered fractional-pixel
     mush. Every STUDENT-FACING size now sits on one of these steps — pick the
     nearest step for anything new instead of minting a value. (/admin keeps
     its legacy values: operator-facing, not part of this scale. The two
     deliberate exceptions are the hub/wizard display clamps and the material
     viewer's 0.9rem mono — see .material-text.) */
  --text-xs: 0.75rem;   /* 12px  — badges, kickers, counts, tags */
  --text-sm: 0.85rem;   /* 13.6px — hints, notes, small chrome */
  --text-base: 0.95rem; /* 15.2px — secondary body, small buttons */
  --text-md: 1rem;      /* 16px  — body, stems, primary buttons */
  --text-lg: 1.2rem;    /* 19.2px — section headings */
  --text-xl: 1.5rem;    /* 24px  — page titles */

  /* LETTER-SPACING — two tokens for the uppercase-microlabel role (was seven
     hand-tuned values, invisible drift one-at-a-time, visible when THE
     PROBLEM / MATERIALS / QUESTION 5 OF 6 stack on one page). Kickers and
     badges take --track-kicker; the big SECTION 9 eyebrows take
     --track-eyebrow. */
  --track-kicker: 0.14em;
  --track-eyebrow: 0.3em;
}

* { box-sizing: border-box; }

/* the hidden attribute always wins, even over display:flex rules */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink);
  background: var(--bg);
}

.brand {
  margin: 0 0 0.25rem;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--muted);
}

.muted { color: var(--muted); }

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em;
  background: var(--bg);
  padding: 0.1em 0.35em;
  border-radius: 4px;
}

/* ---------- landing / terminal pages ---------- */

.landing {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

.landing-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 2rem 2.25rem;
  max-width: 26rem;
  width: 100%;
  text-align: center;
}

.landing-card h1 { margin: 0.25rem 0 0.75rem; font-size: var(--text-xl); }
.landing-card p { margin: 0.25rem 0; }

/* ---------- form page ---------- */

.form-page {
  max-width: 44rem;
  margin: 0 auto;
  padding: 1.25rem 1rem 4rem;
}

.page-head { margin: 1rem 0 1.5rem; }
.page-head h1 {
  margin: 0;
  font-size: var(--text-xl);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.mode-note {
  margin: 0.6rem 0 0;
  color: var(--muted);
  font-size: var(--text-base);
}

.badge {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  padding: 0.25em 0.6em;
  border-radius: 999px;
  vertical-align: middle;
}
.badge-practice { background: #e7f0fb; color: var(--accent); }
.badge-real { background: #eaf6ee; color: var(--ok); }

.section {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem 1.25rem 1.4rem;
  margin-bottom: 1.1rem;
}

.section h2 {
  margin: 0 0 0.9rem;
  font-size: var(--text-lg);
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 50%;
  background: var(--ink);
  color: #fff;
  font-size: var(--text-sm);
  font-weight: 600;
  flex: none;
}

.field { margin-bottom: 1rem; }
.field:last-child { margin-bottom: 0; }

.field > label {
  display: block;
  font-weight: 600;
  margin-bottom: 0.3rem;
}

.hint {
  margin: -0.1rem 0 0.4rem;
  font-size: var(--text-sm);
  color: var(--muted);
}

input[type="text"],
textarea {
  width: 100%;
  font: inherit;
  color: inherit;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
}

textarea { resize: vertical; min-height: 7.5rem; }

input[type="text"]:focus,
textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.field.invalid input,
.field.invalid textarea,
.scenario-list.invalid {
  border-color: var(--danger);
  outline-color: var(--danger);
}

/* ---------- split-screen workspace ---------- */

.workspace-page { min-height: 100dvh; }

/* Desktop: scenario dominant (~2/3, left), form column (~1/3, right),
   each independently scrollable. */
@media (min-width: 901px) {
  .workspace {
    display: grid;
    grid-template-columns: 1fr minmax(24rem, 36%);
    height: 100dvh;
  }
  .scenario-panel,
  .form-col {
    overflow-y: auto;
    min-height: 0;
    overscroll-behavior: contain;
  }
  .form-col {
    border-left: 1px solid var(--line);
    background: var(--bg);
  }
}

.scenario-panel {
  padding: 1.5rem clamp(1.25rem, 4vw, 3rem) 3rem;
  background: var(--surface);
}

.form-col { padding: 1.25rem 1rem 4rem; }
.form-col .page-head { margin: 0.25rem 0 1.25rem; }
.form-col .page-head h1 { font-size: var(--text-xl); }

/* REMOVED 2026-07-29 (GHOST-365): the how-bar's rules (`.how-bar`,
   `.how-steps`, `.how-num`, `.how-note` and their two breakpoints). The
   component is gone from html.ts — its four numbered steps promised a video
   recording and a paste box that no longer exist — and the scenario panel
   now leads with the tabs. Its entries in the dark block went with it. */

/* the pick is reversible — say so, right under the tabs */
.pick-note {
  margin: -0.6rem 0 1.1rem;
  font-size: var(--text-sm);
  color: var(--muted);
}

/* scenario tabs (the selection — pick = view) */

.scenario-tabs {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
  border-radius: 8px;
}
.scenario-tabs.invalid { outline: 2px solid var(--danger); outline-offset: 4px; }

.scenario-tab {
  cursor: pointer;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  border: 1.5px solid var(--line);
  border-radius: 8px;
  background: #fff;
  padding: 0.6rem 1rem;
  min-width: 9rem;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.scenario-tab input { position: absolute; opacity: 0; pointer-events: none; }
.scenario-tab-label { font-weight: 700; font-size: var(--text-base); }
.scenario-tab-title { font-size: var(--text-sm); color: var(--muted); }

.scenario-tab:has(input:checked) {
  border-color: var(--accent);
  background: #f2f8ff;
}
.scenario-tab:has(input:checked) .scenario-tab-label { color: var(--accent); }
.scenario-tab:has(input:focus-visible) { outline: 2px solid var(--accent); }

/* full brief + chooser */

.scenario-full h2 { margin: 0.4rem 0 0.75rem; font-size: var(--text-lg); }
.scenario-full p { max-width: 65ch; }

/* ---------- THE PROBLEM — the loudest block on the page ----------
   Before this existed the task was buried inside the owner's quote and
   students could not tell what they were solving. It leads the panel and
   carries the "Copy brief for AI" action. */

.problem-box {
  border: 2px solid var(--ink);
  border-left-width: 8px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 0 4px 18px rgba(16, 24, 40, 0.1);
  padding: 1.1rem 1.25rem 1.15rem;
  margin: 0 0 1.4rem;
}

.problem-kicker {
  margin: 0 0 0.5rem;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--danger);
}

.problem-line {
  margin: 0;
  font-size: var(--text-lg);
  line-height: 1.45;
  font-weight: 700;
  max-width: 58ch;
}

/* .problem-actions was deleted 2026-07-29 with the global "Copy brief"
   button — copying moved onto every question step (.mcq-copy). The
   .btn-copy-brief / .copy-brief-note rules below now serve that context. */

.btn-copy-brief {
  font: inherit;
  font-size: var(--text-base);
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
  padding: 0.6em 1.15em;
  border-radius: 8px;
  border: 1.5px solid var(--accent);
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
}
.btn-copy-brief:hover { filter: brightness(1.06); }
.btn-copy-brief:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
.copy-brief-icon { font-size: 1.05em; line-height: 1; }

.copy-brief-note {
  margin: 0;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--muted);
}

/* The standalone copy-note that replaced THE PROBLEM box (2026-07-30) —
   sits between the panel h2 and the client card. */
.panel-copy-note {
  margin: 0 0 1.1rem;
}

/* manual-copy fallback — shown only when the device blocks the clipboard */

.copy-fallback {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px dashed var(--line);
}

.copy-fallback-note {
  margin: 0 0 0.45rem;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--danger);
}

.copy-fallback-text {
  width: 100%;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--text-sm);
  line-height: 1.45;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--bg);
  resize: vertical;
}

/* copy feedback */

.copy-toast {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  z-index: 200;
  transform: translate(-50%, 0.75rem);
  opacity: 0;
  max-width: min(30rem, calc(100vw - 2rem));
  padding: 0.7rem 1.1rem;
  border-radius: 999px;
  background: var(--ink);
  color: #fff;
  font-size: var(--text-base);
  font-weight: 600;
  text-align: center;
  box-shadow: 0 6px 24px rgba(16, 24, 40, 0.3);
}
.copy-toast.in {
  opacity: 1;
  transform: translate(-50%, 0);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.copy-toast.error { background: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  .copy-toast.in { transition: none; }
}

.scenario-chooser h2 { margin: 0.4rem 0 0.5rem; font-size: var(--text-lg); }
.scenario-chooser ul { padding-left: 1.2rem; }
.scenario-chooser li { margin-bottom: 0.4rem; max-width: 65ch; }

/* ---------- scenario view model: problem → client → materials ---------- */

.block-kicker {
  margin: 0 0 0.5rem;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--muted);
}

/* the client card */

.client-card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
  padding: 1.1rem 1.2rem;
  margin-bottom: 1.4rem;
}

.client-card h3 {
  margin: 0 0 0.35rem;
  font-size: var(--text-md);
  line-height: 1.4;
  max-width: 65ch;
}

.client-people {
  margin: 0 0 0.9rem;
  color: var(--muted);
  font-size: var(--text-base);
}

.client-voice {
  margin: 0 0 1.1rem;
  padding: 0.1rem 0 0.1rem 1rem;
  border-left: 3px solid var(--accent);
}
.client-voice p {
  margin: 0;
  font-size: var(--text-md);
  font-style: italic;
  color: var(--ink);
}

.client-tools-label {
  margin: 0 0 0.45rem;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--muted);
}

.tool-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.tool-chips li {
  font-size: var(--text-sm);
  font-weight: 600;
  padding: 0.22em 0.7em;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
}

/* materials */

.materials { margin-bottom: 0.5rem; }

.material-list {
  display: grid;
  gap: 0.8rem;
}

.material {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
  padding: 0.9rem 1rem;
}

.material-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.55rem;
}

.material-label { margin: 0; font-size: var(--text-md); }

.material-badge {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  padding: 0.2em 0.6em;
  border-radius: 4px;
  background: var(--bg);
  border: 1px solid var(--line);
  color: var(--muted);
}
.material-badge-email { background: #eef4ff; border-color: #cfe0ff; color: #1c4fa1; }
.material-badge-document { background: #f3f0ff; border-color: #ded5ff; color: #5738b8; }
.material-badge-spreadsheet { background: #edf9f0; border-color: #cfeeda; color: #1b7f3b; }
.material-badge-chat { background: #fff5e9; border-color: #ffe0bd; color: #a35a05; }
.material-badge-photo { background: #fdeef4; border-color: #ffd3e3; color: #a5195b; }

.material-format {
  margin-left: auto;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  color: var(--muted);
}

.material-note {
  margin: 0.5rem 0 0.8rem;
  font-size: var(--text-base);
  color: var(--muted);
  max-width: 60ch;
}

.material-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }

.material-btn {
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  padding: 0.45em 1em;
  border: 1.5px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}
.material-btn:hover { border-color: var(--accent); color: var(--accent); }
.material-btn:focus-visible { outline: 2px solid var(--accent); }
.material-download { border-color: var(--accent); color: var(--accent); }

.material-body {
  margin-top: 0.8rem;
  border-top: 1px solid var(--line);
  padding-top: 0.8rem;
}

.material-text {
  margin: 0;
  max-height: 26rem;
  overflow: auto;
  padding: 0.8rem;
  background: var(--bg);
  border-radius: 8px;
  /* The evidence-reading surface: ten client emails, CSVs and a half-Thai
     chat export are READ here, on low-DPI panels. 0.9rem/1.7 (not a scale
     step — a deliberate exception for dense mono + Thai, whose stacked vowels
     and tone marks need the extra size and leading), with Thai-capable
     fallbacks so the passages never drop to a cramped default mono. */
  font-family: ui-monospace, SFMono-Regular, Menlo, "Sarabun", "Thonburi",
    "Leelawadee UI", "Noto Sans Thai", monospace;
  font-size: 0.9rem;
  line-height: 1.7;
  white-space: pre;
  -webkit-overflow-scrolling: touch;
}

.material-image {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
  border: 1px solid var(--line);
}

/* mobile: stacked + floating "view scenario" */

.view-scenario {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 50;
  font: inherit;
  font-weight: 600;
  font-size: var(--text-base);
  padding: 0.6rem 1rem;
  border-radius: 999px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: #fff;
  box-shadow: 0 2px 10px rgba(16, 24, 40, 0.25);
  cursor: pointer;
}

@media (min-width: 901px) {
  .view-scenario { display: none; }
}

/* REMOVED 2026-07-29 (GHOST-365): the whole video/recorder block —
   `.video-prompt`, `.consent-box`, `.checkline`, `.recorder`, `.video-frame`,
   `.video-placeholder`, `.rec-*`, the `pulse` keyframes and `.no-camera`.
   The video step is gone from the workbook, so none of it has markup to
   style. `.btn-rec` / `.btn-stop` and the `.progress` upload bar went with
   it, below, as did the dark-block overrides for all of the above. */

/* ---------- buttons / submit ---------- */

.btn {
  font: inherit;
  font-weight: 600;
  padding: 0.6rem 1.1rem;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: #fff;
  color: var(--ink);
  cursor: pointer;
  min-height: 44px; /* touch target */
}
.btn:hover { border-color: #b9c2cc; }
.btn:disabled { opacity: 0.5; cursor: default; }

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  width: 100%;
  font-size: var(--text-md);
  padding: 0.8rem 1.1rem;
}
.btn-primary:hover { filter: brightness(1.06); border-color: var(--accent); }

.btn-ghost { background: transparent; }

.section-submit { text-align: center; }

/* MOVED OUT of `.section-submit` on 2026-07-29 — it now sits in the workbook
   rail beside `.wb-nudge`, because validate() writes the combined checklist
   and then navigates, and anything inside the last step is hidden by that
   navigation. Two consequences handled here: it is no longer inside a
   `text-align: center` card (so it aligns with the rail), and it is no
   longer inside a `hidden` step (so the old `min-height` reservation would
   have held 1.4em of dead space above the nav on all eight steps — it is
   `hidden` when empty instead, same as the nudge). */
.submit-status { margin: 0.9rem 0 0; font-size: var(--text-base); line-height: 1.45; }
.submit-status.error {
  color: var(--danger);
  font-weight: 600;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--danger);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--danger) 8%, transparent);
}
.submit-status.ok { color: var(--ok); font-weight: 600; }

/* `.progress` / `.progress-bar` — the video UPLOAD bar — were removed
   2026-07-29 with the video step. NOT to be confused with `.wb-progress` /
   `.wb-progress-text` below, which are the workbook's step header. */

.done { padding: 3rem 1rem; display: flex; justify-content: center; }
.done-id { font-size: var(--text-sm); }

/* ---------- the stepped workbook ----------
   The right column is a wizard: one step on screen at a time, driven by
   form.js (`.wb-step[hidden]`). Everything here is written in TOKENS, so
   `body.lc-dark`'s remap carries it without a single override below — the
   workbook only ever renders on the student-facing (dark) pages, but a
   token-only component cannot go wrong if that changes.

   It sits AFTER the button rules on purpose: .wb-nav has to unpick
   .btn-primary's `width: 100%`, and it does that by source order rather than
   by piling on specificity. */

.wb-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 1rem;
  flex-wrap: wrap;
  margin: 0 0 0.85rem;
}

.wb-progress {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0;
  font-weight: 700;
}

.wb-progress-text { font-size: var(--text-md); }

.wb-count {
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--muted);
}

.wb-dots {
  display: flex;
  align-items: center;
  gap: 0.32rem;
  margin: 0;
}

/* 12px, not the old 8.8px: "how much is left" is the most anxiety-relevant
   readout in a test UI, and at 8.8px the states were a colour-code puzzle —
   green vs gray measure 1.23:1, invisible to the deutan/protan student that
   statistics puts in every room of thirty. The answered state therefore
   carries a ✓ GLYPH (shape, not hue); the future ring rides --line, which
   clears the 3:1 non-text floor on dark. */
.wb-dot {
  position: relative;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: transparent;
}
.wb-dot.past { background: var(--muted); border-color: var(--muted); }
/* Answered questions read as filled even once you've moved on. NOT `.done`
   — that class already belongs to the submitted-screen container
   (`display: flex; padding: 3rem`), and a dot wearing it inherits the lot. */
.wb-dot.answered { background: var(--ok); border-color: var(--ok); }
.wb-dot.answered::after {
  content: "\2713";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 0.5rem;
  line-height: 1;
  font-weight: 700;
  color: var(--accent-ink, #fff);
}
.wb-dot.active {
  background: var(--accent);
  border-color: var(--accent);
  transform: scale(1.35);
}

/* the one honest framing line, above the first question only */
.wb-intro {
  margin: 0 0 0.9rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--line);
  font-size: var(--text-base);
  line-height: 1.45;
  color: var(--muted);
}

.section h2.wb-q-title {
  display: block;
  margin: 0 0 0.7rem;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--accent);
}

.mcq-stem {
  margin: 0 0 1rem;
  font-size: var(--text-md);
  line-height: 1.5;
  font-weight: 600;
  max-width: 46ch;
}

/* The stem is a container of authored paragraphs, one <p> per beat (see
   fillStem in form.js). Typography still comes from .mcq-stem by
   inheritance; only the UA paragraph margin needs replacing with a
   deliberate rhythm, and the last one must not double the container's
   own bottom margin. */
.mcq-stem p {
  margin: 0 0 0.7rem;
}

.mcq-stem p:last-child {
  margin-bottom: 0;
}

.mcq-options {
  display: grid;
  gap: 0.6rem;
  margin-bottom: 1.4rem;
}

/* Hints-only questions (scenario B pilot, 2026-07-30): the options render
   collapsed behind a quiet toggle. Deliberately NOT the primary-button
   costume — hints are an escape hatch, not the designed path, and a bright
   cyan "show hints" would read as a required step. Written in tokens so
   the dark remap needs no override. */
.wb-hints-wrap {
  margin-bottom: 1.4rem;
}

.wb-hints-btn {
  appearance: none;
  background: none;
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 0.5rem 0.8rem;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--muted);
  cursor: pointer;
}

.wb-hints-btn:hover {
  color: var(--ink);
  border-color: var(--accent);
}

.wb-hints-btn[aria-expanded="true"] {
  color: var(--ink);
}

.wb-hints {
  margin-top: 0.7rem;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.wb-hints-note {
  margin: 0 0 0.6rem;
  font-size: var(--text-sm);
  color: var(--muted);
}

.wb-hints ul {
  margin: 0;
  padding-left: 1.1rem;
  display: grid;
  gap: 0.5rem;
}

.wb-hints li {
  font-size: var(--text-sm);
  line-height: 1.5;
}

/* Option cards: full width, generous target (the whole card is the label),
   radio semantics kept — the native input is present and focusable, just
   visually replaced by .mcq-mark. */
.mcq-option {
  position: relative; /* the visually-hidden radio is absolute inside it */
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  padding: 0.85rem 0.9rem;
  min-height: 44px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--field, var(--surface));
  cursor: pointer;
  line-height: 1.45;
}
.mcq-option:hover { border-color: var(--accent); }
.mcq-option.picked {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

.mcq-option input[type="radio"] {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}

.mcq-mark {
  flex: none;
  margin-top: 0.18rem;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: transparent;
}
.mcq-option.picked .mcq-mark {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 0.25rem var(--accent);
}
/* keyboard users must still see where they are */
.mcq-option:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.mcq-text { font-size: var(--text-base); }

.mcq-prompt { margin-top: 0.2rem; }
.mcq-prompt textarea { min-height: 10rem; }

/* Question-scoped material — the canonical artifact this question acts on.
   Same treatment family as the inline material viewer: monospace, token
   colours only, scrolls inside its own box rather than widening the page. */
.mcq-material { margin: 0 0 1rem; }
.mcq-material-label {
  margin: 0 0 0.35rem;
  font-family: var(--font-display, inherit);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
.mcq-material-text {
  margin: 0;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--field, var(--surface));
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-x: auto;
  max-height: 22rem;
  overflow-y: auto;
}

/* The per-question copy action (replaces the panel's global Copy brief). */
.mcq-copy { margin: 0 0 1.2rem; }
.mcq-copy .copy-brief-note { margin-top: 0.4rem; }

/* The free-text "Other" input, revealed when its option is picked. */
.mcq-other { margin: -0.2rem 0 0.4rem; }

/* The transcript-file upload under the paste box. */
.mcq-upload { margin-top: 0.9rem; }
.mcq-upload input[type="file"] {
  display: block;
  width: 100%;
  padding: 0.5rem;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  background: var(--field, var(--surface));
  color: inherit;
  font-size: 0.85rem;
}
.mcq-file-note { margin: 0.4rem 0 0; font-size: 0.82rem; color: var(--muted); }

/* "pick a client first" — step 2 with no scenario chosen */
.wb-pick-note { margin: 0 0 1rem; color: var(--muted); max-width: 46ch; }
.wb-pick-jump { width: 100%; }

/* The wrap-up step's mission recap. Structure, not a dash-spliced sentence:
   the objective leads, the rules are a list, the timebox is the staging
   card's badge. Sits in its own inset panel so it reads as context rather
   than as the first instruction of the form below it. */
.wb-wrapnote {
  margin: -0.25rem 0 1.3rem;
  padding: 0.85rem 0.95rem 0.9rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  background: var(--field, var(--bg));
}

.wb-wrap-objective {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 700;
  line-height: 1.4;
}

.wb-wrap-rules {
  margin: 0.55rem 0 0;
  padding-left: 1.05rem;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--muted);
}
.wb-wrap-rules li { margin-bottom: 0.15rem; }

.wb-wrap-time { margin: 0.7rem 0 0; }

/* The step-level nudge: why Next did not move, in one plain sentence, right
   above the button that did not move. Deliberately NOT the red --danger
   validation treatment — this is a "not yet" for a nervous student in their
   second language, not an error. Token-only, so the dark remap carries it. */
.wb-nudge {
  margin: 0.9rem 0 0;
  padding: 0.55rem 0.75rem;
  border: 1px solid var(--accent);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  font-size: var(--text-base);
  line-height: 1.45;
}

/* The autosave promise, on every step. Quiet by design — it is reassurance,
   not an instruction, and it must never compete with the step's own copy. */
.wb-autosave {
  margin: 0.75rem 0 0;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--muted);
}

.wb-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem;
  margin-top: 0.2rem;
}
.wb-nav .btn { width: auto; flex: 1 1 0; font-size: var(--text-md); }
/* Back gone (step 1) → Next must not stretch across the column. `hidden`
   leaves the button in the DOM, so this keys off the attribute, not
   :only-child (which would never match). */
.wb-nav .btn[hidden] + .btn {
  flex: 0 1 auto;
  margin-left: auto;
  min-width: 11rem;
}
/* Final step: Submit is the action, so Back shrinks to an escape hatch
   rather than sitting under it as a second full-width button. */
.wb-nav.wb-nav-end .btn { flex: 0 1 auto; font-size: var(--text-base); }

@media (max-width: 480px) {
  .form-page { padding: 1rem 0.75rem 3.5rem; }
  .section { padding: 1rem 0.9rem 1.1rem; }
  .page-head h1 { font-size: var(--text-lg); }
}

/* ---------- admin / results ---------- */

.small { font-size: var(--text-sm); }

.admin-page {
  max-width: 62rem;
  margin: 0 auto;
  padding: 1.25rem 1rem 4rem;
}

.admin-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 1rem;
  flex-wrap: wrap;
}
.admin-head .btn { text-decoration: none; display: inline-block; }

.login-form { display: grid; gap: 0.8rem; margin-top: 1rem; }
.login-form input[type="password"] {
  width: 100%;
  font: inherit;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 8px;
}
.login-error { margin: 0; color: var(--danger); font-size: 0.9rem; }

.pill {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  padding: 0.2em 0.65em;
  border-radius: 999px;
}
.pill-ok { background: #eaf6ee; color: var(--ok); }
.pill-bad { background: #fdecec; color: var(--danger); }
.pill-muted { background: var(--line); color: var(--muted); }

.result-head,
.result-row {
  display: grid;
  grid-template-columns: 3rem 1fr 16rem 4.5rem;
  gap: 1rem;
  align-items: center;
}
.result-head { padding: 0 1rem 0.4rem; }
.result-head span:last-child { text-align: right; }

.result-row {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 0.85rem 1rem;
  margin-bottom: 0.6rem;
  color: inherit;
  text-decoration: none;
}
.result-row:hover { border-color: var(--accent); }

.result-rank {
  font-size: 1.2rem;
  font-weight: 700;
  text-align: center;
  color: var(--muted);
}
.result-name { font-weight: 600; }
.result-gate { margin-top: 0.25rem; }
.result-score { font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }

.bars { display: grid; gap: 0.22rem; }
.bar {
  display: grid;
  grid-template-columns: 2.6rem 1fr 1.4rem;
  gap: 0.45rem;
  align-items: center;
  font-size: 0.72rem;
  color: var(--muted);
}
.bar-dim { text-transform: uppercase; letter-spacing: 0.04em; }
.bar-track {
  height: 6px;
  background: var(--line);
  border-radius: 999px;
  overflow: hidden;
}
.bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 999px; }
.bar-val { text-align: right; font-variant-numeric: tabular-nums; }

.backlink { color: var(--accent); text-decoration: none; font-weight: 600; }

.detail-video { width: 100%; max-width: 32rem; border-radius: 8px; background: #0e1116; }

.detail-field { margin-bottom: 1rem; }
.detail-field h3 { margin: 0 0 0.35rem; font-size: 0.95rem; }
.detail-text {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font: inherit;
  font-size: 0.92rem;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.7rem 0.8rem;
  max-height: 24rem;
  overflow-y: auto;
}

.evidence-dim h3 { margin: 0.8rem 0 0.3rem; font-size: 0.95rem; }
.evidence-dim blockquote {
  margin: 0.4rem 0;
  padding: 0.5rem 0.8rem;
  border-left: 3px solid var(--accent);
  background: var(--bg);
  border-radius: 0 8px 8px 0;
  font-size: 0.93rem;
}

.detail-weighted { margin: 0.8rem 0 0; }

.raw-json { margin-top: 0.8rem; }
.raw-json summary { cursor: pointer; color: var(--accent); font-weight: 600; font-size: 0.88rem; }
.raw-json pre {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.7rem 0.8rem;
  overflow-x: auto;
  font-size: 0.8rem;
}

@media (max-width: 700px) {
  .result-head { display: none; }
  .result-row { grid-template-columns: 2.2rem 1fr 3.5rem; }
  .result-bars { grid-column: 1 / -1; }
}

/* ---------- flow layer (hub + walkthrough wizard) ---------- */

#flow-root {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: var(--bg);
  overflow-y: auto;
}

body.flow-open { overflow: hidden; }

.flow-screen {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem 1rem;
}

.flow-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 2rem 1.75rem;
  max-width: 27rem;
  width: 100%;
}

.flow-card h1 { margin: 0.25rem 0 0.75rem; font-size: var(--text-xl); }
.flow-card p { margin: 0 0 0.8rem; }
.flow-card p:last-child { margin-bottom: 0; }

/* --- the walkthrough wizard ---
 *
 * The wizard wears the HUB aesthetic: a near-black panel with a cyan hairline
 * over the animated backdrop, Chakra Petch, cyan-glow heading. These rules are
 * the ones the retired "What will you be steering?" screen used (`.tool-choice`
 * / `.tool-option`) — that screen is gone, its look is not: it is the card
 * treatment the operator picked, retargeted onto the wizard. The "This one
 * counts" interstitial wears the same card.
 *
 * flow.js still renders both with `show(..., true)`, but only for the animated
 * backdrop: the cyan custom properties moved to :root, so the panel is legible
 * with or without hub-mode. The literal fallbacks below are now belt-and-braces.
 */

.wiz-card {
  position: relative;
  z-index: 1;
  min-height: 26rem;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 38rem;
  padding: 2.2rem 2rem 1.8rem;
  background: rgba(26, 31, 38, 0.9);
  border: 1px solid rgba(0, 212, 255, 0.25);
  border-radius: 12px;
  box-shadow: 0 0 40px rgba(0, 212, 255, 0.1);
  color: var(--hub-ink, #e8edf1);
  font-family: "Chakra Petch", -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
}

.wiz-card .hub-eyebrow { font-size: var(--text-xs); margin-bottom: 0.9rem; }

.wiz-body { flex: 1; }
.wiz-body h1 {
  margin: 0 0 1rem;
  font-weight: 700;
  font-size: clamp(1.3rem, 3.2vw, 1.9rem);
  line-height: 1.2;
  color: var(--hub-ink, #e8edf1);
  text-shadow: 0 0 30px rgba(0, 212, 255, 0.18);
}
.wiz-body p {
  margin: 0 0 0.9rem;
  /* Body copy stays in the system sans — the card sets Chakra Petch for its
     chrome (eyebrow, heading, buttons), but paragraphs are the densest
     instructional reading in the product, and the display face is
     headings/labels/badges ONLY (the app's own typography contract). Also
     keeps any future Thai word from falling back mid-paragraph: the subset
     is latin-only. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  font-size: var(--text-md);
  line-height: 1.65;
  color: #c6d0d9;
}
.wiz-body p:last-child { margin-bottom: 0; }

.wiz-dots {
  display: flex;
  gap: 0.45rem;
  margin: 0.4rem 0 1.3rem;
}
.wiz-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: rgba(124, 136, 148, 0.35);
  transition: background 0.15s ease, box-shadow 0.15s ease;
}
.wiz-dot.active {
  background: var(--hub-cyan, #00d4ff);
  box-shadow: 0 0 10px rgba(0, 212, 255, 0.7);
}
.wiz-dot.past { background: var(--hub-cyan-dim, #0099cc); }

.wiz-nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.8rem;
  margin-top: 1.6rem;
}

/* Back/secondary keep the cyan outline; the CONFIRM is the cyan fill —
   one primary-button costume across the whole journey (2026-07-29, GHOST-365
   critique row 11). The wizard's Next used to be a cyan-text outline while
   the workspace's confirm was a cyan fill, and the student crossed that seam
   twice; the fill is also the stronger affordance (10.6:1 label contrast). */
.wiz-nav .btn {
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: rgba(26, 31, 38, 0.85);
  border: 1px solid rgba(0, 212, 255, 0.3);
  color: var(--hub-ink, #e8edf1);
  border-radius: 8px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease,
    transform 0.15s ease, color 0.15s ease;
}
.wiz-nav .btn:hover,
.wiz-nav .btn:focus-visible {
  border-color: var(--hub-cyan, #00d4ff);
  box-shadow: 0 0 24px rgba(0, 212, 255, 0.2);
  transform: translateY(-1px);
  outline: none;
}
.wiz-nav .btn-primary {
  width: auto;
  padding: 0.7rem 1.9rem;
  font-size: var(--text-md);
  background: var(--hub-cyan, #00d4ff);
  border-color: var(--hub-cyan, #00d4ff);
  color: #04141b;
}
.wiz-nav .btn-primary:hover,
.wiz-nav .btn-primary:focus-visible {
  filter: none;
  background: var(--hub-cyan-bright, #4aeaff);
  border-color: var(--hub-cyan-bright, #4aeaff);
  color: #04141b;
}
.wiz-nav .btn-ghost { color: #9aa6b2; }

.wiz-start { width: 100%; margin-top: 0.5rem; }

.interstitial-link { text-align: center; margin-top: 0.9rem; }
.interstitial-link a { color: var(--muted); }
/* on the dark wizard panel the muted grey would disappear */
.wiz-card .interstitial-link a { color: #9aa6b2; }
.wiz-card .interstitial-link a:hover { color: var(--hub-cyan, #00d4ff); }

@media (max-width: 560px) {
  .wiz-card { padding: 1.6rem 1.2rem 1.4rem; min-height: 24rem; }
}

.done-continue { margin-top: 1.2rem; }

/* ---------- the HUB (game-style start screen) ---------- */

/* Chakra Petch — Section 9 display font, self-hosted (OFL, @fontsource) */
@font-face {
  font-family: "Chakra Petch";
  src: url("/assets/fonts/chakra-petch-latin-400-normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Chakra Petch";
  src: url("/assets/fonts/chakra-petch-latin-600-normal.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Chakra Petch";
  src: url("/assets/fonts/chakra-petch-latin-700-normal.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* The Militech-cyan custom properties themselves now live on :root (see the
   top of this file) — the hub is no longer their only consumer. All the
   hub-mode class does is opt this overlay into the canvas background. */
#flow-root.hub-mode { background: var(--hub-canvas); }

.hub {
  position: relative;
  min-height: 100dvh;
  overflow: hidden;
  color: var(--hub-ink);
  font-family: "Chakra Petch", -apple-system, BlinkMacSystemFont, "Segoe UI",
    sans-serif;
}

/* --- backdrop layers (pure CSS, transform/opacity only) --- */

.hub-bg { position: absolute; inset: 0; }

.hub-grid {
  position: absolute;
  inset: -50%;
  background-image:
    linear-gradient(rgba(0, 212, 255, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(0, 212, 255, 0.05) 1px, transparent 1px);
  background-size: 56px 56px;
  animation: hub-grid-pan 120s linear infinite;
}

@keyframes hub-grid-pan {
  to { transform: translate3d(56px, 56px, 0); }
}

.hub-aurora {
  position: absolute;
  width: 70vmax;
  height: 70vmax;
  border-radius: 50%;
  opacity: 0.4;
  will-change: transform;
}
.hub-aurora-1 {
  top: -30vmax;
  left: -18vmax;
  background: radial-gradient(circle at center,
    rgba(0, 212, 255, 0.16) 0%, rgba(0, 212, 255, 0.05) 40%, transparent 65%);
  animation: hub-drift-1 46s ease-in-out infinite alternate;
}
.hub-aurora-2 {
  bottom: -35vmax;
  right: -20vmax;
  background: radial-gradient(circle at center,
    rgba(0, 153, 204, 0.14) 0%, rgba(0, 153, 204, 0.04) 45%, transparent 65%);
  animation: hub-drift-2 58s ease-in-out infinite alternate;
}
.hub-aurora-3 {
  top: 15%;
  right: -30vmax;
  background: radial-gradient(circle at center,
    rgba(74, 234, 255, 0.08) 0%, transparent 60%);
  animation: hub-drift-3 70s ease-in-out infinite alternate;
}

@keyframes hub-drift-1 { to { transform: translate3d(9vmax, 6vmax, 0) scale(1.15); } }
@keyframes hub-drift-2 { to { transform: translate3d(-8vmax, -5vmax, 0) scale(1.1); } }
@keyframes hub-drift-3 { to { transform: translate3d(-10vmax, 4vmax, 0); } }

.hub-particles { position: absolute; inset: 0; }

.hub-particle {
  position: absolute;
  border-radius: 50%;
  background: var(--hub-cyan);
  opacity: 0.35;
  will-change: transform, opacity;
  animation-name: hub-rise, hub-twinkle;
  animation-timing-function: linear, ease-in-out;
  animation-iteration-count: infinite, infinite;
}

@keyframes hub-rise {
  to { transform: translate3d(2vw, -110vh, 0); }
}
@keyframes hub-twinkle {
  50% { opacity: 0.08; }
}

.hub-vignette {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 30% 20%, transparent 0%, rgba(6, 6, 10, 0.55) 90%),
    linear-gradient(rgba(6, 6, 10, 0.2), rgba(6, 6, 10, 0.5));
}

/* --- title (placed like a game title) --- */

.hub-title {
  position: absolute;
  top: clamp(2rem, 9vh, 6rem);
  left: clamp(1.25rem, 6vw, 5rem);
  right: 1.25rem;
}

.hub-eyebrow {
  margin: 0 0 0.4rem;
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: var(--track-eyebrow);
  color: var(--hub-ink);
}
.hub-nine {
  color: var(--hub-cyan);
  text-shadow: 0 0 14px rgba(0, 212, 255, 0.65);
}

.hub-title h1 {
  margin: 0;
  font-weight: 700;
  font-size: clamp(1.7rem, 5.2vw, 3.4rem);
  letter-spacing: 0.02em;
  line-height: 1.1;
  text-shadow: 0 0 30px rgba(0, 212, 255, 0.18);
}

/* Pre-flight under the title: duration + what you need. #a2aeba = 9.4:1 on
   the near-black canvas — quiet next to the title, never below AA. */
.hub-sub {
  margin: 0.7rem 0 0;
  max-width: 34rem;
  font-weight: 400;
  font-size: var(--text-base);
  line-height: 1.5;
  color: #a2aeba;
}
.hub-sub + .hub-sub { margin-top: 0.2rem; }

/* --- menu (lower-left quadrant, game-style) --- */

.hub-menu {
  position: absolute;
  left: clamp(1.25rem, 6vw, 5rem);
  bottom: clamp(2rem, 10vh, 6rem);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.4rem;
  max-width: min(28rem, calc(100vw - 2.5rem));
}

.hub-item {
  font: inherit;
  font-weight: 600;
  font-size: clamp(1.05rem, 2.2vw, 1.35rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: left;
  color: #cfd8de;
  background: none;
  border: 0;
  border-left: 3px solid transparent;
  padding: 0.55rem 0.9rem;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
  min-height: 44px;
}

.hub-item:hover,
.hub-item:focus-visible {
  color: var(--hub-cyan);
  border-left-color: var(--hub-cyan);
  transform: translateX(4px);
  outline: none;
  text-shadow: 0 0 16px rgba(0, 212, 255, 0.5);
}

/* Dimmed but never illegible. #737e8a = 4.9:1 on the canvas, i.e. it clears
   the full small-text 4.5:1 — NOT the large-text 3:1: the mobile block below
   sets `.hub-item { font-size: 1rem }`, so on every phone in the room the
   locked item is 16px, under the 18.66px-bold large-text threshold. The note
   below is small text everywhere and takes #8a95a1 (6.6:1). (Was #55606a =
   3.15:1 on both, then #66717c = 4.1:1, which still failed at 16px: the one
   line explaining the day's gating mechanic was the least legible text on
   the site.) */
.hub-item.locked {
  color: #737e8a;
  cursor: not-allowed;
}
.hub-item.locked:hover,
.hub-item.locked:focus-visible {
  color: #737e8a;
  border-left-color: transparent;
  transform: none;
  text-shadow: none;
}

.lock-icon {
  width: 0.95em;
  height: 0.95em;
  margin-right: 0.5rem;
  vertical-align: -0.1em;
}

.hub-lock-note {
  display: block;
  font-weight: 400;
  font-size: var(--text-xs);
  letter-spacing: 0.06em;
  text-transform: none;
  color: #8a95a1;
  margin-top: 0.15rem;
}

.hub-tag {
  display: inline-block;
  margin-left: 0.6rem;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  color: var(--hub-cyan);
  border: 1px solid rgba(0, 212, 255, 0.45);
  border-radius: 999px;
  padding: 0.1em 0.55em;
  vertical-align: 0.15em;
}

.hub-done {
  margin: 0 0 0.5rem;
  padding: 0.55rem 0.9rem;
  font-size: clamp(1rem, 2vw, 1.2rem);
  color: var(--hub-cyan-bright);
  text-shadow: 0 0 16px rgba(0, 212, 255, 0.4);
}

/* --- profile panel (dark themed card over the backdrop) --- */

.hub-panel {
  position: relative;
  z-index: 1;
  background: rgba(26, 31, 38, 0.92);
  border: 1px solid rgba(0, 212, 255, 0.25);
  color: var(--hub-ink);
  box-shadow: 0 0 40px rgba(0, 212, 255, 0.08);
}

.hub-panel h1 { color: var(--hub-ink); }
.hub-panel .hub-eyebrow { font-size: var(--text-xs); margin-bottom: 0.8rem; }

.profile-rows { margin: 1rem 0 1.3rem; }
.profile-rows > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid rgba(124, 136, 148, 0.2);
}
.profile-rows dt {
  color: var(--hub-muted);
  font-size: var(--text-sm);
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
}
.profile-rows dd { margin: 0; font-weight: 600; }

.profile-actions {
  display: flex;
  justify-content: space-between;
  gap: 0.8rem;
}
.profile-actions .btn-primary { width: auto; padding: 0.6rem 1.6rem; }

.hub-ghost {
  color: var(--hub-ink);
  border-color: rgba(124, 136, 148, 0.5);
  background: transparent;
}
.hub-ghost:hover { border-color: var(--hub-cyan); color: var(--hub-cyan); }

/* The "What will you be steering?" screen (.tool-choice / .tool-option) and
   the post-identity welcome card lived here. The question is gone — its card
   treatment was not thrown away: it is now the wizard's, under `.wiz-card`
   in the flow-layer section above. */

/* --- mobile: menu sits lower-center --- */

@media (max-width: 640px) {
  .hub-menu {
    left: 50%;
    transform: translateX(-50%);
    align-items: center;
    bottom: clamp(2.5rem, 12vh, 5rem);
  }
  .hub-item { text-align: center; font-size: 1rem; white-space: nowrap; }
  .hub-item:hover,
  .hub-item:focus-visible { transform: none; }
  .hub-lock-note { white-space: normal; }
  .hub-title { text-align: center; left: 1rem; right: 1rem; }
}

/* --- reduced motion: land calm and resting, no animation --- */

@media (prefers-reduced-motion: reduce) {
  .hub-grid,
  .hub-aurora,
  .hub-particle { animation: none; }
}

/* ==========================================================================
   PROGRESSIVE-DISCLOSURE STAGING (flow layer)

   The workspace no longer arrives as one data dump. flow.js walks a cold
   student through three stages with at most TWO presses:

     stage 0  an almost-empty staging area — one card where the form column
              will be (the scenario pick + a mission summary), nothing else
     stage 1  the problem / client / materials draw into the LEFT column in a
              staggered reveal; the right column shows a dead workbook preview
     stage 2  the full workspace, unchanged from the un-staged page

   Every rule below is inert without the classes flow.js adds
   (`.lc-flow`, `body.lc-stage-0|1`, `.lc-reveal`) — delete flow.js and the
   page renders exactly as it did before.
   ========================================================================== */

/* --- the stage host shares the form column's grid cell ------------------ */

@media (min-width: 901px) {
  .workspace.lc-flow > .scenario-panel { grid-column: 1; }
  /* the stage host and the form column share the right cell — exactly one
     of them is displayed at a time, so they never actually overlap */
  .workspace.lc-flow > .form-col,
  .workspace.lc-flow > .lc-stage { grid-column: 2; grid-row: 1; }
  .workspace.lc-flow > .lc-stage {
    border-left: 1px solid var(--line);
    background: var(--bg);
  }
}

/* --- stage visibility --------------------------------------------------- */

.lc-stage { display: none; }

body.lc-stage-0 .lc-stage,
body.lc-stage-1 .lc-stage {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 2rem 1.25rem 2.5rem;
  overflow-y: auto;
  min-height: 0;
}

body.lc-stage-0 .form-col,
body.lc-stage-1 .form-col { display: none; }

/* stage 0 — the left side is genuinely empty (children removed from flow,
   so nothing over there is tabbable either) */
body.lc-stage-0 .scenario-panel > * { display: none; }
body.lc-stage-0 .scenario-panel { padding: 0; background: var(--bg); }

/* the mobile "View scenario" affordance has nothing to point at yet */
body.lc-stage-0 .view-scenario,
body.lc-stage-1 .view-scenario { display: none !important; }

/* --- stage 0: the mission card ------------------------------------------ */

.lc-stage-card {
  width: 100%;
  max-width: 27rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(16, 24, 40, 0.09);
  padding: 1.5rem 1.5rem 1.3rem;
}

.lc-stage-kicker {
  margin: 0 0 0.9rem;
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: 0.01em;
}

.lc-stage-client {
  margin: 0 0 0.7rem;
  font-size: var(--text-base);
  line-height: 1.5;
}

.lc-stage-job {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 700;
  line-height: 1.45;
}
.lc-stage-job span {
  display: block;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.2rem;
}

.lc-mini-check {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.4rem;
}
.lc-mini-check li {
  position: relative;
  padding-left: 1.5rem;
  font-size: var(--text-sm);
  line-height: 1.4;
  color: var(--muted);
}
.lc-mini-check li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.28em;
  width: 0.85rem;
  height: 0.85rem;
  border: 1.5px solid var(--line);
  border-radius: 3px;
  background: #fff;
}

/* REMOVED 2026-07-29 (GHOST-365): `.lc-stage-time` and `.lc-badge-time` —
   the "60 MINUTES" pill that rode the staging card, the real-mode mission
   header and the workbook's wrap-up step. There are no timers and no time
   badges anywhere in the student flow; the proctor manages the clock by
   walking the room. Its dark-block override and its `--font-display` entry
   went with it, and `timebox` is gone from the mission data itself. */

.lc-stage-go { width: 100%; margin-top: 1.3rem; }

/* Stage 1's gate before a client is picked. It is `aria-disabled`, not
   `disabled` — the handler still fires and sends the student to the tabs —
   so the demotion has to be carried entirely by the paint: off the cyan
   fill, onto a quiet outline that reads as "not yet". */
.lc-stage-go.is-disabled {
  background: transparent;
  border-color: var(--line);
  color: var(--muted);
  box-shadow: none;
  cursor: default;
}
.lc-stage-go.is-disabled:hover {
  filter: none;
  border-color: var(--line);
}

.lc-skip-line { margin: 0.85rem 0 0; text-align: center; }

.lc-skip {
  font: inherit;
  font-size: var(--text-sm);
  color: var(--muted);
  background: none;
  border: 0;
  padding: 0.25rem 0.4rem;
  text-decoration: underline;
  cursor: pointer;
}
.lc-skip:hover { color: var(--accent); }
.lc-skip:focus-visible { outline: 2px solid var(--accent); border-radius: 4px; }

/* the three clients, named but not yet chosen — stage 0 never forces a
   blind pick, it just says one is coming */
.lc-stage-clients {
  list-style: none;
  /* bottom margin so "YOUR JOB" doesn't butt straight into the third client */
  margin: 0.7rem 0 1rem;
  padding: 0;
  display: grid;
  gap: 0.3rem;
}
.lc-stage-clients li {
  font-size: var(--text-sm);
  color: var(--muted);
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg);
}
.lc-stage-clients strong { color: var(--ink); }

/* --- stage 1: the right-hand preview of the form-to-come ---------------- */

.lc-stage-side { width: 100%; max-width: 25rem; }

.lc-stage-side-note {
  margin: 0 0 1.1rem;
  font-size: var(--text-base);
  color: var(--muted);
}
.lc-stage-side .lc-stage-go { margin-top: 0; }

.lc-silhouette {
  margin-top: 1.6rem;
  display: grid;
  gap: 0.85rem;
  opacity: 0.6;
  pointer-events: none;
  user-select: none;
}

.lc-sil-row { display: grid; gap: 0.35rem; }

.lc-sil-label {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--muted);
}

.lc-sil-bar {
  display: block;
  height: 1.6rem;
  border-radius: 6px;
  background: var(--line);
}
.lc-sil-row.tall .lc-sil-bar { height: 3.2rem; }

/* --- stage 2: the compact mission header (real mode) -------------------- */

.lc-mission-header {
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  background: var(--surface);
  border-radius: var(--radius);
  padding: 0.8rem 1rem 0.9rem;
  margin-bottom: 1rem;
}

.lc-mh-kicker {
  margin: 0 0 0.45rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--muted);
}

.lc-mh-client { margin: 0 0 0.4rem; font-size: var(--text-sm); color: var(--muted); }
.lc-mh-job { margin: 0; font-size: var(--text-base); font-weight: 700; line-height: 1.4; }
.lc-mh-deliv { margin: 0.4rem 0 0; font-size: var(--text-sm); color: var(--muted); }

/* --- the reveal: opacity + transform only, staggered by --lc-i ---------- */

.lc-reveal { opacity: 0; transform: translateY(14px); }
.lc-reveal.lc-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.42s ease, transform 0.42s cubic-bezier(0.22, 0.68, 0.36, 1);
  transition-delay: calc(var(--lc-i, 0) * 160ms);
}

/* --- mobile: stages become vertical reveals ----------------------------- */

@media (max-width: 900px) {
  .lc-silhouette { display: none; }

  body.lc-stage-0 .lc-stage { min-height: 78vh; }

  /* stage 1 on a phone: the gate rides along as a bottom bar, so the button
     is reachable without scrolling past every material card first */
  body.lc-stage-1 .lc-stage {
    position: sticky;
    bottom: 0;
    z-index: 25;
    padding: 0.8rem 1rem;
    background: var(--surface);
    border-top: 1px solid var(--line);
    box-shadow: 0 -4px 16px rgba(16, 24, 40, 0.1);
  }
  body.lc-stage-1 .lc-stage-kicker,
  body.lc-stage-1 .lc-stage-side-note { display: none; }
  body.lc-stage-1 .lc-skip-line { margin-top: 0.5rem; }
}

/* --- reduced motion: everything lands instantly ------------------------- */

@media (prefers-reduced-motion: reduce) {
  .lc-reveal { opacity: 1; transform: none; }
  .lc-reveal.lc-in { transition: none; transition-delay: 0s; }
}

/* --- the review-reset chip (DEV ONLY) ----------------------------------- */
/* Rendered by flow.js only when the page config carries dev:true, which
   comes from DEV_FAKE_RESULTS on the Worker. z-index sits above #flow-root
   (100) so it is reachable on the hub and wizard screens too. */

.lc-reset-chip {
  position: fixed;
  right: 0.75rem;
  bottom: 0.75rem;
  z-index: 120;
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1;
  padding: 0.4em 0.7em;
  border-radius: 999px;
  border: 1px solid rgba(124, 136, 148, 0.5);
  background: rgba(26, 31, 38, 0.82);
  color: #c8d2dc;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.12s ease, border-color 0.12s ease;
}
.lc-reset-chip:hover { opacity: 1; border-color: var(--accent); }
.lc-reset-chip:focus-visible { opacity: 1; outline: 2px solid var(--accent); }

/* keep the mobile "View scenario" button clear of the chip — dev only, so
   production layout is untouched */
body.lc-dev .view-scenario { bottom: 3.1rem; }

/* ==========================================================================
   DARK THEME — the hub aesthetic across the whole student journey
   (`body.lc-dark`, emitted only by formPage() in src/html.ts)

   Before this block the student crossed a seam: the hub and the walkthrough
   wizard were near-black Militech-cyan game screens, and then the staging
   card, the workspace and the done screen arrived as a white utilitarian
   form. Same URL, two products. This block removes the seam.

   THREE RULES IT KEEPS TO:

   1. ONE PALETTE. Every value here derives from the `--hub-*` tokens on
      :root — the hub's own palette. There is no second dark theme.
   2. LEGIBILITY FIRST. These are long-form READING surfaces (briefs,
      material files, Thai text) and long-form WRITING surfaces (five
      textareas). Body copy stays in the system sans at >= 7:1 on its
      background; the lowest-contrast text token here is `--muted` at 7.8:1
      on `--surface`. Chakra Petch (`--font-display`) is applied to headings,
      labels and badges ONLY.
   3. NO STRUCTURAL CHANGE. Colours, borders, shadows and one font-family.
      No layout, no display, no visibility — every JS hook, staged-reveal
      animation, focus target and mobile stack behaves exactly as before.

   WHY IT IS LAST IN THE FILE: the element-level rules below
   (`body.lc-dark .scenario-panel`, …) tie on specificity with the staging
   rules above them, and CSS breaks ties by source order. Where a rule above
   is MORE specific (e.g. `.workspace.lc-flow > .lc-stage`, three classes),
   the override here matches its shape rather than relying on order.

   NOT dark, deliberately: the neutral landing page, the "submissions closed"
   page and the whole of /admin. None of them is on the student's path.
   ========================================================================== */

/* --- the token remap ---------------------------------------------------- */

body.lc-dark {
  /* surfaces: canvas → card → inset field, each step darker inward, which is
     the same direction the light theme reads (page < card, inset < card) */
  --bg: #070a0e;
  --surface: #131922;
  --field: #0b1015;

  --ink: var(--hub-ink);          /* #e8edf1 — 15:1 on --surface */
  --muted: #a2aeba;               /* 7.8:1 on --surface — AA at body sizes.
                                     NOT --hub-muted (#7c8894, 4.9:1): that
                                     one is fine for hub chrome, too thin for
                                     .hint / .material-note paragraphs. */
  --line: #5b6672;                /* 3.0:1 on --surface, 3.3:1 on --field —
                                     the WCAG NON-TEXT floor. Was #2a333d
                                     (1.38:1 / 1.49:1): borders, unselected
                                     radio rings, scenario tabs and the
                                     future progress dots were effectively
                                     invisible on a dim glossy laptop in a
                                     bright room, which is exactly the
                                     hardware screening day runs on. The text
                                     palette was audited from the start; the
                                     non-text palette was not, until
                                     2026-07-29 (GHOST-365). */
  --accent: var(--hub-cyan);      /* 10:1 on --surface */
  --accent-ink: #04141b;          /* on cyan: 10.6:1 */
  --danger: #ff8a8a;              /* 7.8:1 — the light #c62828 is unreadable
                                     here, so the token flips rather than the
                                     twenty places that use it */
  --ok: #58d68d;                  /* 9.6:1 */

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.35);
  --glow: 0 0 40px rgba(0, 212, 255, 0.08);

  /* dark UA chrome: scrollbars on both workspace columns, the consent
     checkbox, date/spin controls, and the form controls' own defaults */
  color-scheme: dark;

  background-color: var(--bg);
  /* A still, very faint echo of the hub's aurora. Deliberately NOT
     `background-attachment: fixed` (iOS repaint jank on a long scroll) and
     deliberately not animated — the hub earns motion, a reading surface does
     not. On desktop the body never scrolls anyway: .workspace is 100dvh with
     per-column overflow. */
  background-image:
    radial-gradient(ellipse 90% 55% at 10% -8%,
      rgba(0, 212, 255, 0.075), transparent 62%),
    radial-gradient(ellipse 70% 50% at 100% 108%,
      rgba(0, 153, 204, 0.055), transparent 60%);
  background-repeat: no-repeat;
}

body.lc-dark ::selection {
  background: rgba(0, 212, 255, 0.28);
  color: var(--ink);
}

/* --- links --------------------------------------------------------------
   app.css has no generic anchor rule, so on dark the UA default (#0000EE,
   visited #551A8B) would render at ~1.3:1. That hits real, shipped links:
   the flow layer's injected "Menu · Review the walkthrough" in the form
   header, the interstitial's "Back", and the material Download buttons. */

body.lc-dark a { color: var(--accent); }
body.lc-dark a:visited { color: var(--accent); }
body.lc-dark a:hover { color: var(--hub-cyan-bright); }
body.lc-dark .interstitial-link a { color: #9aa6b2; }
body.lc-dark .interstitial-link a:hover { color: var(--accent); }
/* .wiz-body p wins over .muted on specificity, so say it here */
body.lc-dark .wiz-body p.muted { color: #9aa6b2; }

/* --- display type: headings, labels and badges ONLY ---------------------- */

body.lc-dark .brand,
body.lc-dark .page-head h1,
body.lc-dark .section h2,
body.lc-dark .scenario-full h2,
body.lc-dark .scenario-chooser h2,
body.lc-dark .client-card h3,
body.lc-dark .material-label,
body.lc-dark .field > label,
body.lc-dark .block-kicker,
body.lc-dark .problem-kicker,
body.lc-dark .client-tools-label,
body.lc-dark .badge,
body.lc-dark .material-badge,
body.lc-dark .pill,
body.lc-dark .step,
body.lc-dark .scenario-tab-label,
body.lc-dark .lc-stage-kicker,
body.lc-dark .lc-stage-job span,
body.lc-dark .lc-mh-kicker,
body.lc-dark .lc-sil-label,
body.lc-dark .landing-card h1,
body.lc-dark .btn-primary,
body.lc-dark .btn-copy-brief,
body.lc-dark .view-scenario {
  font-family: var(--font-display);
}

/* The problem line and the material text are CONTENT, not chrome — they stay
   in the system sans / mono no matter what inherits around them. */
body.lc-dark .problem-line,
body.lc-dark .material-note,
body.lc-dark .copy-brief-note,
body.lc-dark .hint,
body.lc-dark .client-people,
body.lc-dark .client-voice p {
  font-family: inherit;
}

body.lc-dark .brand { color: var(--hub-muted); letter-spacing: var(--track-eyebrow); }
body.lc-dark .page-head h1,
body.lc-dark .landing-card h1,
body.lc-dark .scenario-full h2 {
  text-shadow: 0 0 30px rgba(0, 212, 255, 0.16);
}

/* --- workspace columns --------------------------------------------------
   The scenario panel goes transparent so the body's aurora shows through and
   the cards (--surface) read as cards rather than dissolving into a panel of
   the same colour. The workbook column stays a slightly recessed glass. */

body.lc-dark .scenario-panel { background: transparent; }
body.lc-dark .form-col { background: rgba(6, 8, 12, 0.55); }

@media (min-width: 901px) {
  body.lc-dark .form-col { border-left-color: rgba(0, 212, 255, 0.16); }
  /* three classes above — match the shape, don't rely on source order */
  body.lc-dark .workspace.lc-flow > .lc-stage {
    background: rgba(6, 8, 12, 0.55);
    border-left-color: rgba(0, 212, 255, 0.16);
  }
}

/* --- cards and containers ----------------------------------------------- */

body.lc-dark .landing-card,
body.lc-dark .flow-card:not(.hub-panel) {
  background: rgba(19, 25, 34, 0.92);
  border-color: rgba(0, 212, 255, 0.22);
  box-shadow: var(--shadow), var(--glow);
}

body.lc-dark .section,
body.lc-dark .client-card,
body.lc-dark .material {
  box-shadow: var(--shadow);
}

/* THE PROBLEM keeps its "loudest block" job: on light it borrowed --ink for a
   heavy 2px/8px rule; here that would be a white slab, so the weight comes
   from a cyan edge + glow instead. */
body.lc-dark .problem-box {
  border-color: rgba(0, 212, 255, 0.3);
  border-left-color: var(--accent);
  box-shadow: 0 4px 22px rgba(0, 0, 0, 0.45), 0 0 30px rgba(0, 212, 255, 0.07);
}
/* Fire orange, not --danger: the loudest block on the page must not open
   with the palette's "something is wrong" hue on a surface that also shows
   real red validation states. Doctrine sanctions exactly one warm accent —
   "the single accent, one warm point in a cold frame" — and #ffa366 measures
   9.0:1 on --surface. (GHOST-365 critique row 11.) */
body.lc-dark .problem-kicker { color: #ffa366; }

body.lc-dark .material-btn {
  background: rgba(232, 237, 241, 0.05);
  border-color: var(--line);
}
body.lc-dark .material-btn:hover {
  border-color: var(--accent);
  background: rgba(0, 212, 255, 0.08);
}
body.lc-dark .material-download { border-color: rgba(0, 212, 255, 0.45); }

/* The inline viewer + the manual-copy fallback are the two places a student
   reads raw client data (including the Thai passages, which fall back off
   Chakra Petch's latin subset — so both stay monospace/system). Darker than
   the card, cyan-free, maximum contrast. */
body.lc-dark .material-text,
body.lc-dark .copy-fallback-text {
  background: var(--field);
  border: 1px solid var(--line);
  color: var(--ink);
}
body.lc-dark .copy-fallback { border-top-color: var(--line); }
body.lc-dark .copy-fallback-note { color: var(--danger); }
body.lc-dark .material-image { background: var(--field); }

body.lc-dark code { background: var(--field); color: var(--ink); }

/* --- scenario tabs (the pick) ------------------------------------------- */

body.lc-dark .scenario-tab {
  background: var(--surface);
  border-color: var(--line);
}
body.lc-dark .scenario-tab:hover { border-color: rgba(0, 212, 255, 0.45); }
body.lc-dark .scenario-tab:has(input:checked) {
  border-color: var(--accent);
  background: rgba(0, 212, 255, 0.1);
  box-shadow: 0 0 22px rgba(0, 212, 255, 0.12);
}

/* --- form controls ------------------------------------------------------
   An editable field has to be OBVIOUSLY editable on dark: a distinct inset
   surface, a real border, and a cyan focus ring you cannot miss. */

body.lc-dark input[type="text"],
body.lc-dark textarea {
  background: var(--field);
  border-color: var(--line);      /* #5b6672 — 3.3:1 on --field. Was a local
                                     #333f4b at 1.78:1: the resting outline
                                     of a field a student has not typed in
                                     yet is what they meet FIRST, and it was
                                     below the non-text floor while the focus
                                     ring (which they meet second) was
                                     excellent. */
  color: var(--ink);
}
body.lc-dark input[type="text"]::placeholder,
body.lc-dark textarea::placeholder { color: #8593a0; }

/* hover has to stay a visible step ABOVE the (now stronger) resting border */
body.lc-dark input[type="text"]:hover,
body.lc-dark textarea:hover { border-color: #7c8894; }

/* The unselected radio ring is the ONLY affordance for "here is a choice you
   have not made yet", at 1.15rem on the darkest surface in the app — so it
   gets a stronger ring than the generic hairline (#7c8894 = 5.3:1 on
   --field). The picked state still wins: `.mcq-option.picked .mcq-mark` is
   (0,3,0) against this rule's (0,2,1). */
body.lc-dark .mcq-mark { border-color: #7c8894; }

body.lc-dark input[type="text"]:focus,
body.lc-dark textarea:focus {
  background: #0d141b;
  border-color: var(--accent);
  outline-color: var(--accent);
  box-shadow: 0 0 0 4px rgba(0, 212, 255, 0.12);
}

/* MUST sit here — after the base AND the focus rules, exactly as the light
   theme orders it. `[type="text"]` scores in the CLASS column, so
   `body.lc-dark input[type="text"]` is (0,2,2) and outranks
   `.field.invalid input` (0,2,1): without this the failed-validation
   highlight would silently do nothing on the three text inputs while still
   firing on the four textareas, and validate() would say "fill in the
   highlighted fields" with nothing highlighted. */
body.lc-dark .field.invalid input[type="text"],
body.lc-dark .field.invalid textarea,
body.lc-dark .scenario-list.invalid {
  border-color: var(--danger);
  outline-color: var(--danger);
}

/* Chrome ignores autocomplete="off" on a field labelled "Your name" and
   paints its own near-white autofill background — a literal light flash in
   the middle of the workbook. */
body.lc-dark input:-webkit-autofill,
body.lc-dark input:-webkit-autofill:hover,
body.lc-dark input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--ink);
  caret-color: var(--ink);
  box-shadow: 0 0 0 100px var(--field) inset;
}

/* --- buttons ------------------------------------------------------------
   Two tiers, both from the hub: PRIMARY is a cyan fill with near-black text
   (the confirm key), SECONDARY is the wizard's cyan-outline-on-gunmetal. */

body.lc-dark .btn {
  background: rgba(232, 237, 241, 0.06);
  border-color: var(--line);
  color: var(--ink);
}
body.lc-dark .btn:hover {
  border-color: rgba(0, 212, 255, 0.55);
  background: rgba(0, 212, 255, 0.08);
}
body.lc-dark .btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

body.lc-dark .btn-primary,
body.lc-dark .btn-copy-brief,
body.lc-dark .view-scenario {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
  letter-spacing: 0.02em;
}
body.lc-dark .btn-primary:hover,
body.lc-dark .btn-copy-brief:hover,
body.lc-dark .view-scenario:hover {
  background: var(--hub-cyan-bright);
  border-color: var(--hub-cyan-bright);
  filter: none;
  box-shadow: 0 0 26px rgba(0, 212, 255, 0.35);
}
body.lc-dark .btn-copy-brief:focus-visible { outline-color: var(--ink); }
body.lc-dark .view-scenario { box-shadow: 0 2px 14px rgba(0, 0, 0, 0.55); }

/* The wizard's own nav is ALREADY the hub aesthetic (cyan outline on
   gunmetal) and the operator picked it — the generic .btn / .btn-primary
   rules above are more specific than the ones that built it, so restate it
   here rather than let the workspace tier bleed backwards into the wizard. */
body.lc-dark .wiz-nav .btn {
  background: rgba(26, 31, 38, 0.85);
  border-color: rgba(0, 212, 255, 0.3);
  color: var(--hub-ink);
  letter-spacing: 0.06em;
}
body.lc-dark .wiz-nav .btn:hover,
body.lc-dark .wiz-nav .btn:focus-visible {
  background: rgba(26, 31, 38, 0.85);
  border-color: var(--accent);
  box-shadow: 0 0 24px rgba(0, 212, 255, 0.2);
}
body.lc-dark .wiz-nav .btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
body.lc-dark .wiz-nav .btn-primary:hover,
body.lc-dark .wiz-nav .btn-primary:focus-visible {
  background: var(--hub-cyan-bright);
  border-color: var(--hub-cyan-bright);
  color: var(--accent-ink);
  box-shadow: 0 0 24px rgba(0, 212, 255, 0.25);
}
body.lc-dark .wiz-nav .btn-ghost { color: #9aa6b2; }

/* `.btn-rec` / `.btn-stop` dark rules removed 2026-07-29 with the recorder. */
body.lc-dark .btn-ghost { background: transparent; }

body.lc-dark .step {
  background: var(--accent);
  color: var(--accent-ink);
}

/* --- badges, pills and chips -------------------------------------------
   The five material kinds are colour-CODED, so the hues survive: each keeps
   its hue and swaps a pale fill + dark text for a translucent fill + light
   text. Every foreground below is >= 8:1 on --surface. */

body.lc-dark .badge-practice {
  background: rgba(0, 212, 255, 0.14);
  color: var(--accent);
  box-shadow: inset 0 0 0 1px rgba(0, 212, 255, 0.38);
}
body.lc-dark .badge-real {
  background: rgba(88, 214, 141, 0.14);
  color: var(--ok);
  box-shadow: inset 0 0 0 1px rgba(88, 214, 141, 0.38);
}

body.lc-dark .material-badge {
  background: rgba(232, 237, 241, 0.07);
  border-color: var(--line);
  color: var(--muted);
}
body.lc-dark .material-badge-email {
  background: rgba(138, 180, 255, 0.13);
  border-color: rgba(138, 180, 255, 0.38);
  color: #8ab4ff;
}
body.lc-dark .material-badge-document {
  background: rgba(192, 174, 255, 0.13);
  border-color: rgba(192, 174, 255, 0.38);
  color: #c0aeff;
}
body.lc-dark .material-badge-spreadsheet {
  background: rgba(127, 217, 154, 0.13);
  border-color: rgba(127, 217, 154, 0.38);
  color: #7fd99a;
}
body.lc-dark .material-badge-chat {
  background: rgba(240, 180, 106, 0.13);
  border-color: rgba(240, 180, 106, 0.38);
  color: #f0b46a;
}
body.lc-dark .material-badge-photo {
  background: rgba(247, 154, 192, 0.13);
  border-color: rgba(247, 154, 192, 0.38);
  color: #f79ac0;
}

body.lc-dark .tool-chips li {
  background: rgba(232, 237, 241, 0.05);
  border-color: var(--line);
  color: var(--muted);
}

/* the progress pills on the hub Profile panel */
body.lc-dark .pill-ok { background: rgba(88, 214, 141, 0.15); color: var(--ok); }
body.lc-dark .pill-bad { background: rgba(255, 138, 138, 0.15); color: var(--danger); }
body.lc-dark .pill-muted { background: rgba(232, 237, 241, 0.09); color: var(--muted); }

/* --- stage 0: the staging card ------------------------------------------
   Same treatment as .wiz-card, which is where the student just came from. */

body.lc-dark .lc-stage-card {
  background: rgba(19, 25, 34, 0.92);
  border-color: rgba(0, 212, 255, 0.25);
  box-shadow: 0 10px 34px rgba(0, 0, 0, 0.5), var(--glow);
}
body.lc-dark .lc-stage-kicker { color: var(--ink); }
body.lc-dark .lc-stage-clients li {
  background: rgba(232, 237, 241, 0.04);
  border-color: var(--line);
  color: var(--muted);
}
body.lc-dark .lc-stage-clients strong { color: var(--ink); }
body.lc-dark .lc-mini-check li::before {
  background: var(--field);
  border-color: rgba(0, 212, 255, 0.4);
}
body.lc-dark .lc-skip { color: var(--muted); }
body.lc-dark .lc-skip:hover { color: var(--accent); }

/* stage 1: the dead silhouette of the form to come */
body.lc-dark .lc-sil-bar { background: rgba(232, 237, 241, 0.09); }

/* Stage 1's gate, before a client is picked. The light-theme `.is-disabled`
   rule loses to `body.lc-dark .btn-primary`'s cyan fill on specificity, so
   the demotion has to be restated here — otherwise the "not yet" state is
   painted exactly like the live one. */
body.lc-dark .lc-stage-go.is-disabled,
body.lc-dark .lc-stage-go.is-disabled:hover {
  background: rgba(232, 237, 241, 0.04);
  border-color: var(--line);
  color: var(--muted);
  box-shadow: none;
  filter: none;
}

/* mobile stage 1 — the gate rides along as a sticky bottom bar */
body.lc-dark.lc-stage-1 .lc-stage {
  background: rgba(11, 16, 21, 0.96);
  border-top-color: rgba(0, 212, 255, 0.2);
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.55);
}

/* --- real mode: the compact mission header ------------------------------ */

body.lc-dark .lc-mission-header {
  background: rgba(19, 25, 34, 0.9);
  border-color: rgba(0, 212, 255, 0.22);
  border-left-color: var(--accent);
}

/* --- the copy toast ----------------------------------------------------- */

body.lc-dark .copy-toast {
  background: rgba(19, 25, 34, 0.97);
  color: var(--ink);
  border: 1px solid rgba(0, 212, 255, 0.35);
  box-shadow: 0 6px 28px rgba(0, 0, 0, 0.6);
}
body.lc-dark .copy-toast.error {
  background: rgba(19, 25, 34, 0.97);
  border-color: rgba(255, 138, 138, 0.55);
  color: var(--danger);
}

/* --- the dev reset chip ------------------------------------------------- */

body.lc-dark .lc-reset-chip { color: var(--muted); }

/* --- the lockdown screen (post-cutoff closed state) ---------------------
   Server-rendered cousin of the hub (html.ts lockdownPage): it reuses the
   hub backdrop classes above and adds a centered banner. New selectors
   only — nothing here overrides the dark remap, so living below it is
   safe. Entrance is a staggered rise (transform/opacity), then three
   quiet loops: the halo breath, the status dot blink, and the backdrop's
   own drift. Order matters within this block: the element rules override
   the `.lockdown-center > *` stagger by source order (equal specificity). */

.lockdown { background: var(--hub-canvas); }

.lockdown-center {
  position: relative; /* above the absolutely-positioned backdrop */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 2rem 1.25rem;
}

/* The breathing cyan glow behind the banner. Static transform centers it;
   only opacity animates. */
.lockdown-halo {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 90vmin;
  height: 90vmin;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle at center,
    rgba(0, 212, 255, 0.13) 0%, rgba(0, 212, 255, 0.05) 40%, transparent 62%);
  animation: ld-halo 6s ease-in-out infinite alternate;
  pointer-events: none;
}
@keyframes ld-halo {
  from { opacity: 0.55; }
  to { opacity: 1; }
}

/* Staggered entrance. The halo is positioned out of flow, so flow children
   are: eyebrow, title, rule, status, sub, sub. */
.lockdown-center > * {
  opacity: 0;
  animation: ld-in 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}
.lockdown-center > .lockdown-halo { animation: ld-halo 6s ease-in-out infinite alternate; opacity: 1; }
.lockdown-center > .hub-eyebrow { animation-delay: 0.05s; }
.lockdown-center > .lockdown-title { animation-delay: 0.2s; }
.lockdown-center > .lockdown-status { animation-delay: 0.65s; }
.lockdown-center > .lockdown-sub { animation-delay: 0.85s; }
.lockdown-center > .lockdown-sub + .lockdown-sub { animation-delay: 1s; }
@keyframes ld-in {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: translateY(0); }
}

.lockdown-title {
  margin: 0;
  font-weight: 700;
  font-size: clamp(2.1rem, 6.5vw, 4.2rem);
  line-height: 1.08;
  text-transform: uppercase;
  color: var(--hub-ink);
  text-shadow: 0 0 34px rgba(0, 212, 255, 0.28), 0 0 90px rgba(0, 212, 255, 0.12);
  animation-name: ld-in, ld-track;
  animation-duration: 0.7s, 1.1s;
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
  animation-fill-mode: forwards, forwards;
}
/* One-shot cinematic tracking-in on the title (letter-spacing relayouts,
   but once, on one element, at page open — acceptable). */
@keyframes ld-track {
  from { letter-spacing: 0.16em; }
  to { letter-spacing: 0.03em; }
}

/* The cyan hairline draws itself under the title. */
.lockdown-rule {
  width: min(30rem, 72vw);
  height: 2px;
  margin: 1.2rem 0 1.1rem;
  background: linear-gradient(90deg, transparent, var(--hub-cyan), transparent);
  box-shadow: 0 0 14px rgba(0, 212, 255, 0.55);
  animation: ld-rule 0.8s 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}
@keyframes ld-rule {
  from { opacity: 0; transform: scaleX(0); }
  to { opacity: 1; transform: scaleX(1); }
}

.lockdown-status {
  margin: 0;
  font-weight: 700;
  font-size: var(--text-sm);
  letter-spacing: var(--track-kicker);
  text-transform: uppercase;
  color: var(--hub-cyan);
  text-shadow: 0 0 16px rgba(0, 212, 255, 0.4);
}
.lockdown-dot {
  display: inline-block;
  width: 0.55em;
  height: 0.55em;
  margin-right: 0.6em;
  border-radius: 50%;
  vertical-align: 8%;
  background: var(--hub-cyan);
  box-shadow: 0 0 10px rgba(0, 212, 255, 0.8);
  animation: ld-blink 1.6s ease-in-out infinite;
}
@keyframes ld-blink {
  50% { opacity: 0.15; }
}

/* Body copy opts back out of Chakra Petch (legibility rule: the display
   face is headings/labels/badges only). #a2aeba = 9.4:1 on the canvas. */
.lockdown-sub {
  margin: 0.4rem 0 0;
  max-width: 36rem;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-weight: 400;
  font-size: var(--text-base);
  line-height: 1.6;
  color: #a2aeba;
}
.lockdown-status + .lockdown-sub { margin-top: 1.1rem; }

@media (prefers-reduced-motion: reduce) {
  .lockdown-center > *,
  .lockdown-title,
  .lockdown-rule,
  .lockdown-halo,
  .lockdown-dot {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .lockdown-halo { transform: translate(-50%, -50%); opacity: 0.7; }
  /* flow.js skips spawning particles under reduced motion; the server-
     rendered ones would sit as frozen dots, so hide them instead. */
  .lockdown .hub-particle { display: none; }
}
