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

html, body {
  height: 100%;
  background: var(--bg);
  color: var(--ink);
}

body {
  font-family: var(--font-body);
  transition: background 0.2s ease, color 0.2s ease;
}

/* Mount for the shared player. The shell body is just <div id="app">;
   player.js injects .player into it. It must fill the viewport so the
   player's height:100% chain (and the absolutely-positioned .slide) has a
   sized ancestor — in the old self-contained players .player was a direct
   child of <body>, so this restores that height context. */
#app { height: 100%; }

.player {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.slide-container {
  flex: 1;
  position: relative;
  overflow: hidden;
}

.slide {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 4vh 8vw;
  opacity: 0;
  transition: opacity 0.5s ease;
  pointer-events: none;
  background: var(--bg);
  color: var(--ink);
}
.slide.active { opacity: 1; pointer-events: auto; }

.slide.has-image {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  color: var(--onimage-ink);
}
.slide.has-image::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: radial-gradient(ellipse at center, var(--onimage-scrim-from) 0%, var(--onimage-scrim-to) 100%);
}
.slide.has-image > * { position: relative; z-index: 2; }

/* ============================================================
   Register I — Source-authoritative
   ============================================================ */

/* teaching-caption — the reserved hero treatment. Only sourced truths get this. */
.slide-teaching-caption .kicker {
  font-family: var(--font-ui);
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--accent);
  margin-bottom: 2vh;
}
.slide.has-image.slide-teaching-caption .kicker { color: var(--onimage-ink-dim); }
.slide-teaching-caption h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.9rem, 4.4vw, 3.6rem);
  line-height: 1.18;
  max-width: 32ch;
  margin-bottom: 3vh;
}
.slide-teaching-caption .body {
  font-family: var(--font-body);
  font-size: clamp(1rem, 1.7vw, 1.25rem);
  line-height: 1.55;
  color: var(--ink-dim);
  max-width: 42ch;
}
.slide.has-image.slide-teaching-caption .body { color: var(--onimage-ink-dim); }

/* definition (was concept) — sourced definition, boxed. Register-I weight, panel-delimited. */
.slide-definition .definition-label {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--accent);
  margin-bottom: 2.5vh;
}
.slide.has-image.slide-definition .definition-label { color: var(--onimage-ink-dim); }
.slide-definition .definition-box {
  background: var(--bg-panel);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  padding: 2.5vh 3vw;
  margin: 0 auto 3vh;
  max-width: 42ch;
}
.slide.has-image .definition-box {
  background: rgba(20, 26, 22, 0.55);
  border-color: rgba(244, 237, 224, 0.28);
  backdrop-filter: blur(2px);
}
.slide-definition .box-text {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.1rem, 2vw, 1.55rem);
  line-height: 1.4;
  color: var(--ink);
}
.slide.has-image .box-text { color: var(--onimage-ink); }
.slide-definition .body {
  font-family: var(--font-body);
  font-size: clamp(0.92rem, 1.5vw, 1.1rem);
  line-height: 1.55;
  color: var(--ink-dim);
  max-width: 40ch;
}
.slide.has-image.slide-definition .body { color: var(--onimage-ink-dim); }

/* consolidation — folds summary + list. Order-aware structure, Register-I authority. */
.slide-consolidation h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.5rem, 3vw, 2.3rem);
  color: var(--ink);
  margin-bottom: 4vh;
  max-width: 34ch;
}
.slide-consolidation .consolidation-label {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--accent);
  margin-bottom: 2vh;
}
/* list-shaped consolidation (ordered, numbered) */
.slide-consolidation .consolidation-list {
  list-style: none;
  text-align: left;
  max-width: 42ch;
}
.slide-consolidation .consolidation-list .c-item {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  margin-bottom: 2.2vh;
}
.slide-consolidation .consolidation-list .c-num {
  font-family: var(--font-ui);
  font-weight: 600;
  color: var(--accent);
  min-width: 1.6rem;
}
.slide-consolidation .consolidation-list .c-text {
  font-family: var(--font-body);
  font-size: clamp(0.95rem, 1.6vw, 1.15rem);
  line-height: 1.5;
  color: var(--ink-dim);
}
.slide-consolidation .consolidation-list .c-text strong { color: var(--ink); font-weight: 600; }
/* grid-shaped consolidation (unordered review) */
.slide-consolidation .consolidation-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.4vh 2.5vw;
  max-width: 52rem;
  text-align: left;
}
.slide-consolidation .consolidation-grid .c-item {
  font-family: var(--font-body);
  font-size: clamp(0.9rem, 1.4vw, 1.05rem);
  line-height: 1.5;
  color: var(--ink-dim);
}
.slide-consolidation .consolidation-grid .c-item::before {
  content: '\2014';
  color: var(--accent);
  margin-right: 0.5em;
  font-weight: 600;
}

/* ============================================================
   Register II — Held-up-for-examination
   ============================================================ */

/* misconception-held-up — the new container. A belief the course refutes.
   Always lighter than teaching-caption; the "lighter" is bought with scale,
   quotation and containment — never with sub-AA contrast (§S2/§S4). */
.slide-misconception-held-up .misconception-frame {
  font-family: var(--font-ui);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--ink-dim);
  margin-bottom: 2.5vh;
}
.slide.has-image.slide-misconception-held-up .misconception-frame { color: var(--onimage-ink-dim); }
.slide-misconception-held-up .misconception-quote {
  position: relative;
  font-family: var(--font-ui);
  font-weight: 400;
  font-style: italic;
  font-size: clamp(1.15rem, 2.4vw, 1.85rem);
  line-height: 1.4;
  color: var(--ink-dim);
  max-width: 28ch;
  padding: 0 0 0 1.2rem;
  border-left: 3px solid var(--rule-strong);
  text-align: left;
}
.slide.has-image .misconception-quote {
  color: var(--onimage-ink-dim);
  border-left-color: rgba(244, 237, 224, 0.35);
}
.slide-misconception-held-up .misconception-body {
  font-family: var(--font-body);
  font-size: clamp(0.85rem, 1.4vw, 1rem);
  line-height: 1.5;
  color: var(--ink-dim);
  max-width: 38ch;
  margin-top: 3vh;
  opacity: 0.85;
}
.slide.has-image.slide-misconception-held-up .misconception-body { color: var(--onimage-ink-dim); }

/* options — alternatives held for comparison, non-quiz. Equal weight, all below Register I. */
.slide-options h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  color: var(--ink);
  margin-bottom: 4vh;
  max-width: 34ch;
}
.slide.has-image.slide-options h2 { color: var(--onimage-ink); }
.options-container {
  display: flex;
  flex-direction: column;
  gap: 1.4vh;
  max-width: 42rem;
  width: 100%;
}
.option {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  background: var(--bg-panel);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 1.6vh 2vw;
  text-align: left;
}
.slide.has-image .option {
  background: rgba(20, 26, 22, 0.5);
  border-color: rgba(244, 237, 224, 0.2);
}
.option-letter {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 1rem;
  color: var(--accent);
  background: var(--bg-panel-2);
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.option-content h3 {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: clamp(0.92rem, 1.5vw, 1.05rem);
  color: var(--ink);
  margin-bottom: 0.3vh;
}
.slide.has-image .option-content h3 { color: var(--onimage-ink); }
.option-content p {
  font-family: var(--font-body);
  font-size: clamp(0.82rem, 1.3vw, 0.95rem);
  line-height: 1.4;
  color: var(--ink-dim);
}
.slide.has-image .option-content p { color: var(--onimage-ink-dim); }

/* scene — establishing / narrative beat. Image-forward, minimal low-weight text. */
.slide-scene .scene-label {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--ink-dim);
  margin-bottom: 1.5vh;
}
/* DEMO-POLISH-2026-07-24: measured (contrast-check.js), not assumed — the
   label unpaneled (matching .definition-label's precedent of sitting
   outside its box) fails AA on the busiest scene slide: 3.12-3.45:1 against
   this image's warm mid-tones, vs the 4.5:1 small-text floor. Swapping to
   the brighter --onimage-ink token instead would pass by measurement here,
   but every other on-image kicker/label in this file uses the dim ink
   consistently (a register-hierarchy convention, not a one-off) — panel it
   like the paragraph instead of breaking that pattern for scene alone. */
.slide.has-image.slide-scene .scene-label {
  color: var(--onimage-ink-dim);
  display: inline-block;
  background: rgba(20, 26, 22, 0.55);
  border: 1px solid rgba(244, 237, 224, 0.28);
  border-radius: 3px;
  padding: 0.5em 0.9em;
  backdrop-filter: blur(2px);
}
.slide-scene p {
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.05rem, 2vw, 1.55rem);
  line-height: 1.45;
  color: var(--ink-dim);
  max-width: 38ch;
}
/* DEMO-POLISH-2026-07-24: the whole-slide scrim (.slide.has-image::before)
   is a radial gradient, weakest at center — exactly where centered scene
   text sits — so it can't carry text contrast alone (Sean's device note).
   Default treatment: extend the same glass-panel vocabulary already used
   for .definition-box/.option/.citation-chip on images (identical rgba
   pair) to the scene paragraph itself, giving it its own local contrast
   independent of what's behind it. The label above gets the same panel
   for the same reason, measured — see that rule's comment. */
.slide.has-image.slide-scene p {
  color: var(--onimage-ink);
  background: rgba(20, 26, 22, 0.55);
  border: 1px solid rgba(244, 237, 224, 0.28);
  border-radius: 3px;
  padding: 2.5vh 3vw;
  backdrop-filter: blur(2px);
}
.slide-scene em { color: var(--accent); font-style: normal; font-weight: 600; }
.slide.has-image.slide-scene em { color: var(--onimage-ink); text-decoration: underline; text-decoration-color: rgba(244,237,224,0.5); }

/* ============================================================
   Register III — Transitional
   ============================================================ */

/* reveal — the turn. A deliberate visual break: II gives way to I. */
.slide-reveal {
  background: linear-gradient(180deg, var(--accent-wash) 0%, transparent 55%);
}
.slide.has-image.slide-reveal::after {
  content: '';
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, rgba(224,168,196,0.14) 0%, transparent 60%);
}
.slide-reveal .kicker {
  font-family: var(--font-ui);
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--accent);
  margin-bottom: 2vh;
}
.slide.has-image.slide-reveal .kicker { color: var(--onimage-ink-dim); }
.slide-reveal h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.8rem, 4.2vw, 3.4rem);
  line-height: 1.2;
  color: var(--ink);
  max-width: 30ch;
  margin-bottom: 3vh;
}
.slide.has-image.slide-reveal h2 { color: var(--onimage-ink); }
.slide-reveal .body {
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(1rem, 1.8vw, 1.3rem);
  line-height: 1.45;
  color: var(--accent);
  max-width: 36ch;
}
.slide.has-image.slide-reveal .body { color: var(--onimage-ink-dim); }

/* callback — connective reference. Echoes an earlier beat at reduced weight. */
.slide-callback { opacity: 0.98; }
.slide-callback .callback-label {
  font-family: var(--font-ui);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  color: var(--ink-dim);
  opacity: 0.75;
  margin-bottom: 1.5vh;
}
.slide.has-image.slide-callback .callback-label { color: var(--onimage-ink-dim); }
.slide-callback p {
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(0.98rem, 1.8vw, 1.35rem);
  line-height: 1.45;
  color: var(--ink-dim);
  max-width: 36ch;
  opacity: 0.92;
}
.slide.has-image.slide-callback p { color: var(--onimage-ink); }
.slide-callback em { color: var(--accent); font-style: normal; font-weight: 600; }
.slide.has-image.slide-callback em { color: var(--onimage-ink); text-decoration: underline; text-decoration-color: rgba(244,237,224,0.5); }

/* ============================================================
   Shape axis — orthogonal to register (§S3). A shape class rides
   alongside the type/register classes on the same .slide element,
   so — same rule as has-image — compound it (.slide.shape-x), never
   space it, or it silently falls back to the type's default shape.
   ============================================================ */

/* figure — a quantity leads at scale; the rest of the line is caption. */
.slide.shape-figure .figure-value {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(3.4rem, 9vw, 7.5rem);
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 2vh;
}
.slide.has-image.shape-figure .figure-value { color: var(--onimage-ink); }
.slide.shape-figure .figure-caption {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.3rem, 2.6vw, 2rem);
  line-height: 1.3;
  color: var(--ink);
  max-width: 26ch;
  margin-bottom: 3vh;
}
.slide.has-image.shape-figure .figure-caption { color: var(--onimage-ink); }

/* sequence — ordered steps as a visible progression, not a stacked list.
   Rows are computed in render() (see seqRows()), not fixed at 3-per-row:
   a trailing single-item row reads as an orphan, so a 7-item sequence
   lays out 3+4 rather than 3+3+1. Each row is its own grid (column count
   set inline per row), so the connector below only needs :not(:last-child)
   — no nth-child row-size math, correct for any row size. */
.slide-consolidation .consolidation-sequence {
  display: flex;
  flex-direction: column;
  gap: 4vh;
  max-width: 58rem;
  width: 100%;
}
.slide-consolidation .consolidation-sequence .seq-row {
  display: grid;
  gap: 1.5vw;
}
.slide-consolidation .consolidation-sequence .seq-step {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1vh;
}
.slide-consolidation .consolidation-sequence .seq-num {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg);
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 1.05rem;
  flex-shrink: 0;
}
.slide-consolidation .consolidation-sequence .seq-text {
  font-family: var(--font-body);
  font-size: clamp(0.85rem, 1.3vw, 1rem);
  line-height: 1.45;
  color: var(--ink-dim);
  max-width: 17ch;
}
.slide-consolidation .consolidation-sequence .seq-text strong { color: var(--ink); font-weight: 600; }
.slide-consolidation .consolidation-sequence .seq-step:not(:last-child)::after {
  content: '';
  position: absolute;
  top: 1.25rem;
  left: calc(50% + 1.55rem);
  width: calc(100% - 1.5rem);
  height: 2px;
  background: var(--rule-strong);
}
@media (max-width: 768px) {
  .slide-consolidation .consolidation-sequence .seq-row { grid-template-columns: 1fr !important; }
  .slide-consolidation .consolidation-sequence .seq-step::after { display: none; }
}

/* Phone readability (MOBILE-READABILITY-2026-07-23). The ≤768px collapse
   above traded width for height inside a frame with no height to give
   (.slide-container was overflow:hidden, nothing scrolled) — dense
   consolidation slides clipped, and consolidation-grid's auto-fit was
   sizing itself to max-width rather than the flex parent's actual width
   (.slide uses align-items:center, not stretch, so nothing constrained it),
   centering an oversized grid and spilling cards off both edges. */
@media (max-width: 640px) {
  /* Sean's explicit design: six steps read as two columns, row-major,
     1.2/3.4/5.6 — flatten .seq-row (display:contents) so the six .seq-step
     children become direct grid items of .consolidation-sequence in DOM
     order; grid auto-placement alone then guarantees that reading order,
     rather than hoping two independently-sized rows-of-3 reflow evenly. */
  .slide-consolidation .consolidation-sequence {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2vh 4vw;
  }
  .slide-consolidation .consolidation-sequence .seq-row { display: contents; }
  /* Sized for the old single-row horizontal layout; loosen for the ~140-
     160px columns here so text uses the actual available width instead of
     wrapping narrower than it needs to. Connector line stays suppressed —
     inherited from the ≤768px rule above (640 is a subset of that range),
     and a row-major 2-col wrap has no clean way to draw one regardless
     (it would need to skip every other step to avoid pointing into the
     next row down). */
  .slide-consolidation .consolidation-sequence .seq-text { max-width: none; }

  /* Same family, same fix: force exactly 2 columns instead of the
     auto-fit/260px-floor sizing that produced the overflow above. Odd
     item counts (M1: 7, M2: 8, M3: 8) leave the last row half-empty —
     acceptable; not worth replicating seqRows()'s desktop orphan-folding
     for a plain review grid that never had it. consolidation-list (a
     numbered paragraph list, not card/step-like — M2 slide 16) is
     deliberately not touched here; it relies on the scroll backstop below
     like any other slide type nobody's specifically restructured. */
  .slide-consolidation .consolidation-grid { grid-template-columns: repeat(2, 1fr); }

  /* Systemic backstop: dense slides scroll instead of clip. Scoped to
     phone widths only — desktop stays overflow:hidden, unchanged. */
  .slide-container { overflow-y: auto; overflow-x: hidden; }
  /* .slide centers its content vertically; centered content that overflows
     pushes half the excess ABOVE its own top edge, which a scroll
     container can't reach (scrollTop can't go negative) — the classic
     "centered overflow is half unreachable" bug. `safe center` falls back
     to start-alignment exactly when content overflows, leaving short
     slides centered as today. Verified against both Chromium and WebKit
     (Playwright) before relying on it: plain `center` left the first node
     unreachable at the top; `safe center` did not, in both engines. Plain
     `center` stays as the fallback for any engine that doesn't parse the
     `safe` keyword. */
  .slide { justify-content: center; justify-content: safe center; }
}

/* stark-cut — one or two lines at large scale, little or no elaboration.
   Its cost: the reveal's usual body is de-emphasized, small and quiet,
   rather than dropped (content stays verbatim — §M1 of the brief).
   white-space is deliberate: nl2br() is a pre-existing no-op (its regex
   matches a literal "\n" pair, not the real newline the JSON already
   produces), so every heading's intended breaks have always silently
   collapsed to natural wrap. Invisible at teaching-caption/reveal scale;
   not at this scale, where natural wrap can turn two lines into three.
   Scoped to this shape only — flagged, not fixed deck-wide (out of pass). */
.slide.shape-stark-cut h2 {
  font-size: clamp(2.4rem, 6vw, 4.5rem);   /* was clamp(2.6rem, 7vw, 5.5rem) — fits 4-line stark-cut headings + caption */
  line-height: 1.08;
  max-width: 20ch;
  white-space: pre-line;
}
.slide.shape-stark-cut .body {
  margin-top: 4.5vh;
  font-family: var(--font-body);
  font-size: clamp(0.85rem, 1.3vw, 1rem);
  font-style: normal;
  color: var(--ink-dim);
  opacity: 0.8;
  max-width: 32ch;
}
.slide.has-image.shape-stark-cut .body { color: var(--onimage-ink-dim); opacity: 1; }

/* ============================================================
   Register IV — Frame and assessment
   ============================================================ */

.slide-title {
  background: var(--bg);
}
.slide-title .module-label {
  font-family: var(--font-ui);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.28em;
  color: var(--accent);
  margin-bottom: 2.5vh;
}
.slide-title h1 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2rem, 5vw, 4rem);
  line-height: 1.12;
  color: var(--ink);
  margin-bottom: 2vh;
  max-width: 20ch;
}
.slide-title .subtitle {
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(0.95rem, 1.6vw, 1.25rem);
  color: var(--ink-dim);
}
.slide-title .brand {
  position: absolute;
  bottom: 4vh;
  font-family: var(--font-ui);
  font-size: 0.68rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--ink-dim);
  opacity: 0.7;
}

.quiz-container { max-width: 42rem; width: 100%; }
.quiz-container h2 {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--accent);
  margin-bottom: 2vh;
}
.quiz-question {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.15rem, 2.2vw, 1.6rem);
  line-height: 1.4;
  color: var(--ink);
  margin-bottom: 3.5vh;
}
.quiz-options { display: flex; flex-direction: column; gap: 1.2vh; margin-bottom: 2.5vh; }
.quiz-option {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  background: var(--bg-panel);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 1.5vh 2vw;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.quiz-option:hover { border-color: var(--accent); }
.quiz-option.selected { border-color: var(--accent); background: var(--accent-wash-strong); }
.quiz-option.correct { border-color: var(--correct); background: rgba(31,122,77,0.09); }
.quiz-option.incorrect { border-color: var(--incorrect); background: rgba(179,38,30,0.09); opacity: 0.75; }
.quiz-option .option-letter {
  font-family: var(--font-ui);
  font-weight: 600;
  color: var(--accent);
  background: var(--bg-panel-2);
  width: 2rem; height: 2rem;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.quiz-option.correct .option-letter { background: var(--correct); color: #fff; }
.quiz-option.incorrect .option-letter { background: var(--incorrect); color: #fff; }
.quiz-option .option-text { font-family: var(--font-body); font-size: clamp(0.9rem, 1.5vw, 1.05rem); color: var(--ink); }
.quiz-feedback {
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(0.85rem, 1.4vw, 1rem);
  line-height: 1.5;
  color: var(--ink-dim);
  padding: 1.6vh 1.6vw;
  background: var(--bg-panel);
  border-left: 3px solid var(--accent);
  text-align: left;
}

/* close — was results. The module's resting note, equipped not merely informed. */
.close-container { max-width: 34rem; }
.close-container h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.5rem, 3vw, 2.3rem);
  color: var(--ink);
  margin-bottom: 2.5vh;
}
.close-score {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  margin: 1vh 0 2.5vh;
}
.close-score.pass { color: var(--correct); }
.close-score.fail { color: var(--incorrect); }
.close-message {
  font-family: var(--font-body);
  font-size: clamp(0.98rem, 1.7vw, 1.2rem);
  line-height: 1.5;
  color: var(--ink-dim);
  margin-bottom: 4vh;
}
.close-actions { display: flex; justify-content: center; gap: 1.25rem; flex-wrap: wrap; }
.close-note {
  margin-top: 3vh;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--ink-dim);
}

/* ============================================================
   Chrome — nav, progress, buttons (identical across every course, §S4)
   ============================================================ */

.bottom-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.4vh 2.5vw;
  background: var(--bg-panel);
  border-top: 1px solid var(--rule);
  font-family: var(--font-ui);
}
.bottom-bar-left { display: flex; align-items: center; gap: 0.6rem; position: relative; }

.nav-btn {
  font-family: var(--font-ui);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  padding: 0.65rem 1.1rem;
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.nav-btn:hover:not(:disabled) { background: var(--accent); color: #fff; border-color: var(--accent); }
.nav-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.nav-btn[aria-pressed="true"] { background: var(--accent); color: #fff; border-color: var(--accent); }
.nav-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The three controls below (#modeToggleBtn, #darkToggleBtn, the Menu link)
   live once in the DOM and are reparented by player.js's arrangeBarControls()
   between .bottom-bar-left (desktop, direct flex children, original order)
   and #moreMenu (phone popover, brief's required order) — actual DOM
   relocation, not a CSS reorder. (`order`/flex-direction:*-reverse would
   only change paint position: Tab always follows DOM order, so a CSS-only
   reorder would put one of the two contexts' tab sequence out of step with
   what's on screen.) At desktop #moreMenu is left empty, so plain
   `display: none` is enough — nothing inside it needs preserving.
   (Declared after .nav-btn so this display:none override wins the cascade —
   same specificity, later wins.) */
.bar-more-btn { display: none; }
.bar-more-menu { display: none; }

.progress-section { display: flex; align-items: center; gap: 0.85rem; }
.progress-track { width: 180px; height: 5px; background: var(--rule); border-radius: 3px; overflow: hidden; }
.progress-fill { height: 100%; background: var(--accent); border-radius: 3px; transition: width 0.25s ease; width: 0%; }
.progress-text { font-size: 0.78rem; color: var(--ink-dim); min-width: 3.5rem; text-align: center; }
.nav-buttons { display: flex; gap: 0.6rem; }

.citation-chip {
  position: absolute;
  right: 1.5vw;
  bottom: 1.5vh;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-family: var(--font-ui);
  font-size: 0.72rem;
  color: var(--ink-dim);
  background: var(--bg-panel);
  border: 1px solid var(--rule-strong);
  border-radius: 3px;
  padding: 0.4em 0.7em;
  text-decoration: none;
}
.slide.has-image .citation-chip {
  /* Restores position:absolute against the scrim-lifting rule above
     (.slide.has-image > *, (0,2,0)) — this selector is (0,3,0) and already
     wins outright; without this, the chip drops into normal flow and lands
     wherever the centered content stack ends (DEMO-POLISH-2026-07-24). */
  position: absolute;
  background: rgba(20,26,22,0.65);
  border-color: rgba(244,237,224,0.3);
  color: var(--onimage-ink-dim);
}
.citation-chip:hover { color: var(--accent); border-color: var(--accent); }

@media (max-width: 768px) {
  .slide { padding: 3vh 6vw; }
  .progress-track { width: 100px; }
  .nav-btn { font-size: 0.72rem; padding: 0.55rem 0.8rem; }
  .options-container, .quiz-options { max-width: 90vw; }
}

/* Phone nav (MOBILE-NAV-2026-07-23). Below 768px the bar already tightens
   (above); below this breakpoint there just isn't room for Menu + Dark +
   Learner Mode + a usable progress track + Prev/Next in one row, so the
   three left-cluster controls collapse into the ⋯ popover. Chosen at
   640px per the brief; verified comfortable down to 360px. Stacking order
   inside the popover (Learner/Reviewer toggle, Dark toggle, Menu) comes
   from player.js's arrangeBarControls() moving the real nodes into #moreMenu
   in that order — nothing to set here. */
@media (max-width: 640px) {
  .bar-more-btn { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; }

  .bar-more-menu {
    display: none;
    position: absolute;
    bottom: 100%;
    left: 0;
    margin-bottom: 0.5rem;
    flex-direction: column;
    align-items: stretch;
    gap: 0.35rem;
    background: var(--bg-panel);
    border: 1px solid var(--rule-strong);
    border-radius: 3px;
    padding: 0.5rem;
    min-width: 180px;
    z-index: 20;
  }
  .bar-more-menu.open { display: flex; }
  /* align-items: stretch (above) makes every popover item full-width;
     buttons center their text by default, the anchor doesn't. */
  .bar-menu-link { text-align: center; }

  /* All bar/popover tap targets ≥44px — the shared ≤768px sizing above
     doesn't clear that bar on its own. */
  .nav-btn { min-height: 44px; font-size: 0.75rem; padding: 0.5rem 0.65rem; }

  /* Progress track flexes into whatever room the ⋯ trigger and Prev/Next
     leave, instead of holding a fixed width regardless of available space. */
  .progress-section { flex: 1 1 auto; min-width: 0; }
  .progress-track { flex: 1 1 auto; width: auto; max-width: 180px; min-width: 60px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; }
}
