/* ============================================================================
   Tutor — stylesheet
   ============================================================================

   Carries forward the reference POC's paper-and-ink look (warm cream paper,
   serif headings, green for the child, white for the tutor, orange CTA) and
   fixes the two failures recorded in docs/poc-findings.md:

     FIX 1  Every control inside a lesson is a large touch target. The POC
            styled only the chrome, so quiz answers rendered as ~24px native
            buttons. Here .lesson-btn / .choice-option / .pill / .slider are
            first-class components with 44–56px minimums and explicit
            pressed / selected / correct / incorrect states.

     FIX 2  The chat is reachable on a tablet. Two columns on wide screens;
            below 900px it becomes a bottom-docked bar that expands on tap,
            instead of falling ~1000px below the lesson.

   Also: visible focus rings everywhere (never `outline: none`),
   prefers-reduced-motion, and prefers-color-scheme: dark.

   Hand-written, no framework, no build step.
   ============================================================================ */

/* ----------------------------------------------------------------- fonts --- */

/*
  Two self-hosted variable fonts, used only by the public landing page.
  ────────────────────────────────────────────────────────────────────
  Self-hosted rather than linked from Google, for three reasons that all point the same way: the CSP
  in worker.ts is `default-src 'self'` and a font host would need an exemption; a third-party request
  on a children's product is a privacy question nobody needs to be asked; and the landing page is the
  one page whose visitors have no session, so it must not depend on anything but this origin.

  These are the upstream "latin" subsets, unmodified, under the SIL Open Font License — see
  public/fonts/OFL.txt, which ships beside them because the licence requires it. `unicode-range`
  matches the subset actually downloaded, so anything outside it (the ← and → glyphs, for instance)
  falls back down the stack instead of rendering as tofu.

  `font-display: swap` on purpose: text drawn in the fallback face immediately beats no text at all,
  and the fallback stacks below are metrically close enough that the swap is not a lurch.
*/

@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/fonts/fraunces-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
    U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
    U+FFFD;
}

@font-face {
  font-family: "Caveat";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/caveat-latin-var.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304,
    U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------------------------------------------------------------- tokens --- */

:root {
  /* Paper and ink, from the reference. */
  --bg: #fbf7ef;
  --paper: #ffffff;
  --paper-sunk: #faf7f0;
  --ink: #1f1b16;
  --muted: #6b6256;
  --rule: #e6dfce;
  --rule-strong: #d5cbb2;

  /* Green = the child. Orange = "go here next". */
  --accent: #2d6a4f;
  --accent-ink: #1d4a36;
  --accent-soft: #d8f0e2;
  --accent-line: #b9dec7;
  --warm: #c9540a;
  --warm-dark: #a3460a;
  --warm-soft: #fdeedd;
  /* Text drawn on top of --warm / --accent fills. Flips in dark mode, where
     the accents lighten and white-on-orange would fall below 4.5:1. */
  --on-warm: #ffffff;
  --on-accent: #ffffff;

  /* Answer states. Deliberately not red/green alone — shape and glyph carry it too. */
  --good: #1f7a4d;
  --good-soft: #dcf3e5;
  --good-line: #9fd8b8;
  --try: #9a4a12;
  --try-soft: #fbe9d8;
  --try-line: #edc39a;

  --focus: #0b62d6;

  /* Exercise-book furniture, used only by the landing page: the faint blue horizontal rules of a
     workbook page and the red vertical line down its margin. */
  --paper-rule: #c9d6e8;
  --margin-line: #d8453c;

  --shadow: 0 1px 0 rgba(0, 0, 0, 0.04), 0 8px 24px rgba(60, 40, 10, 0.06);
  --shadow-lift: 0 2px 0 rgba(0, 0, 0, 0.05), 0 14px 32px rgba(60, 40, 10, 0.12);

  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
  /* The child's own voice on the landing page. System faces stay in the stack behind Caveat: they
     are what renders during the swap, and what renders for any glyph outside the latin subset. */
  --hand: "Caveat", "Segoe Print", "Bradley Hand", "Chalkboard SE", "Marker Felt", cursive;

  /*
    Display face for the landing page only.

    Deliberately NOT folded into --serif, which every heading in the signed-in app uses: swapping
    that would restyle the whole product on the way past, which is a bigger decision than a landing
    page. Promote it there in one edit if the app should follow.
  */
  --display: "Fraunces", "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;

  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-pill: 999px;

  /* Touch-target floors. Referenced rather than repeated so they cannot drift. */
  --tap: 48px;
  --tap-lg: 56px;

  /* Height of the collapsed chat dock on narrow screens. */
  --dock-h: 72px;

  --sim-ink: #3a3227;
  --sim-face: #f3ecdd;
  --sim-object: #d9a552;
  --sim-object-line: #a8762b;
  --sim-ground: #6b5433;
  --sim-cool: #4a7fb5;
  --sim-hot: #c9540a;
  --sim-neutral: #8b8272;
}

/*
  Dark palette.
  ─────────────
  Declared twice on purpose, and the two blocks MUST stay in sync:

    1. under `prefers-color-scheme: dark`, gated on `:not([data-theme="light"])` so a page that has
       explicitly asked for light does not get overridden by the OS;
    2. under `[data-theme="dark"]`, so an explicit choice wins in the other direction too.

  CSS has no way to share one declaration block between a media query and an attribute selector, and
  the alternative — a second set of variable names indirected through var() — costs more clarity than
  the duplication does. Change one, change the other.
*/

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #17140f;
    --paper: #211d17;
    --paper-sunk: #1b1812;
    --ink: #f3ecdd;
    --muted: #b3a892;
    --rule: #3a3227;
    --rule-strong: #4b4132;

    --accent: #6ec49a;
    --accent-ink: #b7e6cd;
    --accent-soft: #234034;
    --accent-line: #35604c;
    --warm: #f2914e;
    --warm-dark: #ffa869;
    --warm-soft: #3a2716;
    --on-warm: #24150a;
    --on-accent: #10261b;

    --good: #7fd3a5;
    --good-soft: #21402f;
    --good-line: #3c6a4f;
    --try: #f0ab6d;
    --try-soft: #3d2a17;
    --try-line: #6d4a26;

    --focus: #8ab7ff;

    --paper-rule: #2b3441;
    --margin-line: #e8756b;

    --shadow: 0 1px 0 rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
    --shadow-lift: 0 2px 0 rgba(0, 0, 0, 0.35), 0 14px 32px rgba(0, 0, 0, 0.45);

    --sim-ink: #e8dfcc;
    --sim-face: #2a251d;
    --sim-object: #d9a552;
    --sim-object-line: #f0c884;
    --sim-ground: #a08a63;
    --sim-cool: #7fb2e0;
    --sim-hot: #f2914e;
    --sim-neutral: #9c9382;
  }
}

/* Copy 2 of the dark palette — see the note above. Keep identical to the block above. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #17140f;
  --paper: #211d17;
  --paper-sunk: #1b1812;
  --ink: #f3ecdd;
  --muted: #b3a892;
  --rule: #3a3227;
  --rule-strong: #4b4132;

  --accent: #6ec49a;
  --accent-ink: #b7e6cd;
  --accent-soft: #234034;
  --accent-line: #35604c;
  --warm: #f2914e;
  --warm-dark: #ffa869;
  --warm-soft: #3a2716;
  --on-warm: #24150a;
  --on-accent: #10261b;

  --good: #7fd3a5;
  --good-soft: #21402f;
  --good-line: #3c6a4f;
  --try: #f0ab6d;
  --try-soft: #3d2a17;
  --try-line: #6d4a26;

  --focus: #8ab7ff;

  --paper-rule: #2b3441;
  --margin-line: #e8756b;

  --shadow: 0 1px 0 rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-lift: 0 2px 0 rgba(0, 0, 0, 0.35), 0 14px 32px rgba(0, 0, 0, 0.45);

  --sim-ink: #e8dfcc;
  --sim-face: #2a251d;
  --sim-object: #d9a552;
  --sim-object-line: #f0c884;
  --sim-ground: #a08a63;
  --sim-cool: #7fb2e0;
  --sim-hot: #f2914e;
  --sim-neutral: #9c9382;
}

:root[data-theme="light"] {
  color-scheme: light;
}

/* --------------------------------------------------------------- reset-ish - */

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

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

html,
body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--serif);
  /* The POC was 18px serif prose throughout and read as text-dense. Body copy
     stays comfortable, but lesson UI is sans and larger. */
  font-size: 17px;
  line-height: 1.55;
  min-height: 100vh;
  overflow-x: hidden;
}

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

/* Focus. Never removed — only replaced with something more visible. */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
  border-radius: 4px;
}
/* Keep a fallback ring for browsers without :focus-visible. */
:focus:not(:focus-visible) {
  outline: none;
}

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

/* ---------------------------------------------------------------- typography */

h1,
h2,
h3 {
  font-family: var(--serif);
  letter-spacing: -0.01em;
  text-wrap: balance;
}
h1 {
  font-size: clamp(1.7rem, 1.2rem + 2.2vw, 2.25rem);
  margin: 0 0 0.35rem;
  line-height: 1.15;
}
h2 {
  font-size: 1.35rem;
  margin: 1.7rem 0 0.6rem;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid var(--rule);
}
h3 {
  font-size: 1.1rem;
  margin: 1.2rem 0 0.4rem;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
a:hover {
  color: var(--warm);
}

.hint {
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
  margin: 0.35rem 0 0;
}

/* -------------------------------------------------------------------- layout */

.app {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 24px;
  max-width: 1240px;
  margin: 0 auto;
  padding: 24px 20px 40px;
  align-items: start;
}

.app-main {
  min-width: 0;
}

.card {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}

.lesson {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  padding: 24px clamp(16px, 3vw, 32px) 32px;
}

.lesson-head {
  margin-bottom: 1rem;
}

.subject-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
}
.subject-tag {
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  padding: 3px 10px;
}

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

.crumbs {
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
  margin: 0 0 14px;
}

.crumbs.trail {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px;
  padding: 6px 10px;
  background: var(--warm-soft);
  border: 1px solid var(--try-line);
  border-radius: var(--r-pill);
  width: fit-content;
  max-width: 100%;
}
.crumbs.trail .crumb {
  color: var(--try);
  text-decoration: none;
  padding: 6px 8px;
  border-radius: var(--r-sm);
  min-height: 32px;
  display: inline-flex;
  align-items: center;
}
.crumbs.trail .crumb:hover {
  background: rgba(0, 0, 0, 0.06);
  color: var(--warm);
}
.crumbs.trail .crumb-current {
  color: var(--ink);
  font-weight: 600;
  padding: 6px 8px;
}
.crumbs.trail .crumb-sep {
  color: var(--rule-strong);
  padding: 0 1px;
}
.crumbs.trail .crumb-back {
  margin-left: 8px;
  padding: 10px 16px;
  /* 44px like everything else. It is the control a lost child reaches for first. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  background: var(--accent);
  color: var(--on-accent);
  border-radius: var(--r-pill);
  text-decoration: none;
  font-weight: 700;
  font-size: 0.85rem;
}
.crumbs.trail .crumb-back:hover {
  background: var(--warm);
  color: var(--on-warm);
}

/* ======================================================================
   FIX 1 — lesson controls
   Every one of these is a real <button>, at least 44px tall, most 48–56px.
   ====================================================================== */

.lesson-btn {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-height: var(--tap);
  padding: 12px 20px;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.2;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--rule-strong);
  border-radius: var(--r-md);
  box-shadow: 0 2px 0 var(--rule-strong);
  cursor: pointer;
  transition: transform 0.08s ease, background 0.12s ease, border-color 0.12s ease,
    box-shadow 0.12s ease;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.lesson-btn:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.lesson-btn:active:not(:disabled) {
  transform: translateY(2px);
  box-shadow: none;
}
.lesson-btn[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.lesson-btn:disabled {
  opacity: 0.55;
  cursor: default;
  box-shadow: none;
}

.lesson-btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 2px 0 var(--accent-ink);
}
.lesson-btn--primary:hover:not(:disabled) {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
  color: var(--on-accent);
}

.lesson-btn--cta {
  background: var(--warm);
  border-color: var(--warm);
  color: var(--on-warm);
  box-shadow: 0 2px 0 var(--warm-dark);
  font-size: 1.05rem;
  min-height: var(--tap-lg);
  padding: 14px 22px;
  width: 100%;
  text-decoration: none;
}
.lesson-btn--cta:hover:not(:disabled) {
  background: var(--warm-dark);
  border-color: var(--warm-dark);
  color: var(--on-warm);
}

.lesson-btn--icon {
  min-width: var(--tap);
  padding: 12px 14px;
  font-size: 1.25rem;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
}

/* ------------------------------------------------------------------- blocks */

.block {
  margin: 0 0 22px;
}
.block:last-child {
  margin-bottom: 0;
}

.block-prompt {
  font-family: var(--serif);
  font-size: 1.2rem;
  font-weight: 600;
  line-height: 1.35;
  margin: 0 0 12px;
  text-wrap: pretty;
}

.block-text p {
  margin: 0 0 0.75em;
  max-width: 62ch;
  text-wrap: pretty;
}
.block-text p:last-child {
  margin-bottom: 0;
}
.block-text strong {
  font-weight: 700;
}

.review-flag {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  padding: 3px 10px;
  margin-bottom: 8px;
}

/* Figure ------------------------------------------------------------------- */

/*
  A figure is a PICTURE. It must not look like a device.

  It used to be styled identically to `.sim` — same sunken background, same
  1px rule, same radius, same caption underneath — so the only way to tell a
  thing you can drag from a thing you cannot was to try. Children tried: one
  afternoon of real use logged 70 `interact_miss` events (click-no-handler and
  drag-attempt on bare `svg`/`circle`/`path`) against 22 real interactions, and
  the child concluded the app was broken.

  So the two get different visual languages, and the distinction is carried by
  the FRAME rather than by any text, because the children this is for are five
  to eleven and a label they have to read is not an affordance:

    - a sim is a device — bordered panel, its own surface, controls beneath
    - a figure is an illustration — no frame, sitting in the flow of the prose,
      with an italic centred caption the way a picture in a book has one

  The artwork keeps a soft tinted plate behind it. That is for contrast, not
  containment: figure SVGs are model-authored and some are drawn in pale
  colours that vanish against white. It is deliberately borderless, tighter in
  radius, and inset from the text column, so it reads as part of the picture
  rather than as a panel around it.
*/
.figure {
  background: none;
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
}
.figure > svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: 60vh;
  padding: 10px;
  background: var(--paper-sunk);
  border-radius: var(--r-sm);
}
.figure figcaption {
  font-family: var(--sans);
  font-size: 0.88rem;
  font-style: italic;
  text-align: center;
  color: var(--muted);
  margin-top: 8px;
}

/* Choice ------------------------------------------------------------------- */

.choice-list {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.choice-option {
  -webkit-appearance: none;
  appearance: none;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--tap-lg);
  padding: 14px 16px;
  font-family: var(--sans);
  font-size: 1.05rem;
  font-weight: 500;
  line-height: 1.3;
  text-align: left;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--rule-strong);
  border-radius: var(--r-md);
  box-shadow: 0 2px 0 var(--rule-strong);
  cursor: pointer;
  transition: transform 0.08s ease, background 0.12s ease, border-color 0.12s ease,
    box-shadow 0.12s ease;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.choice-option:hover:not(:disabled) {
  border-color: var(--accent);
  background: var(--accent-soft);
}
.choice-option:active:not(:disabled) {
  transform: translateY(2px);
  box-shadow: none;
}

.choice-option .choice-mark {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid var(--rule-strong);
  font-family: var(--sans);
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--muted);
  background: var(--paper-sunk);
}
.choice-option .choice-label {
  flex: 1 1 auto;
  min-width: 0;
}

/* Selected (pressed) — before grading is revealed. */
.choice-option[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.choice-option[aria-pressed="true"] .choice-mark {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

/* Graded states. Colour is never the only signal: the mark glyph changes too. */
.choice-option.is-correct {
  border-color: var(--good);
  background: var(--good-soft);
  box-shadow: 0 2px 0 var(--good-line);
}
.choice-option.is-correct .choice-mark {
  border-color: var(--good);
  background: var(--good);
  color: var(--on-accent);
}
.choice-option.is-incorrect {
  border-color: var(--try);
  background: var(--try-soft);
  box-shadow: 0 2px 0 var(--try-line);
}
.choice-option.is-incorrect .choice-mark {
  border-color: var(--try);
  background: var(--try);
  color: var(--on-warm);
}
/* The right answer, revealed after a wrong pick — outlined, not filled, so it
   reads as "here it is" rather than "you got this". */
.choice-option.is-answer {
  border-color: var(--good);
  border-style: dashed;
  background: var(--paper);
}
.choice-option.is-answer .choice-mark {
  border-color: var(--good);
  color: var(--good);
  background: var(--paper);
}
.choice-option.is-muted:not(.is-answer) {
  opacity: 0.6;
}

/* Feedback. No penalty framing anywhere — "not quite, try again", never "wrong". */
.feedback {
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.45;
  margin-top: 12px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--rule);
  background: var(--paper-sunk);
  white-space: pre-line;
}
.feedback:empty {
  display: none;
}
.feedback--good {
  border-color: var(--good-line);
  background: var(--good-soft);
  color: var(--good);
}
.feedback--try {
  border-color: var(--try-line);
  background: var(--try-soft);
  color: var(--try);
}

/* Order -------------------------------------------------------------------- */

.order-list {
  display: grid;
  gap: 10px;
  margin: 0 0 4px;
  padding: 0;
  list-style: none;
  touch-action: pan-y;
}

.order-item {
  -webkit-appearance: none;
  appearance: none;
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: var(--tap-lg);
  padding: 12px 14px;
  font-family: var(--sans);
  font-size: 1.05rem;
  font-weight: 500;
  text-align: left;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--rule-strong);
  border-radius: var(--r-md);
  box-shadow: 0 2px 0 var(--rule-strong);
  cursor: grab;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
  transition: transform 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}
.order-item:hover:not(:disabled) {
  border-color: var(--accent);
}
.order-item .order-index {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--paper-sunk);
  border: 2px solid var(--rule-strong);
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--muted);
}
.order-item .order-grip {
  flex: 0 0 auto;
  margin-left: auto;
  color: var(--muted);
  font-size: 1.2rem;
  line-height: 1;
  letter-spacing: 0.1em;
}
/* Picked by tap — the tap-to-swap path, which must work without any dragging. */
.order-item.is-picked {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
  transform: scale(1.015);
}
.order-item.is-picked .order-index {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}
.order-item.is-dragging {
  cursor: grabbing;
  box-shadow: var(--shadow-lift);
  opacity: 0.95;
  z-index: 2;
}
.order-item.is-correct {
  border-color: var(--good);
  background: var(--good-soft);
}
.order-item.is-incorrect {
  border-color: var(--try);
  background: var(--try-soft);
}

/* Match -------------------------------------------------------------------- */

.match-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px 14px;
  align-items: start;
}
.match-col {
  display: grid;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.match-col-head {
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 2px;
}

.match-item {
  -webkit-appearance: none;
  appearance: none;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--tap-lg);
  padding: 12px 14px;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 500;
  text-align: left;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--rule-strong);
  border-radius: var(--r-md);
  box-shadow: 0 2px 0 var(--rule-strong);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s ease, border-color 0.12s ease;
}
.match-item:hover:not(:disabled) {
  border-color: var(--accent);
}
.match-item.is-picked {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.match-item.is-paired {
  border-style: solid;
}
.match-item.is-correct {
  border-color: var(--good);
  background: var(--good-soft);
}
.match-item.is-incorrect {
  border-color: var(--try);
  background: var(--try-soft);
}
.match-item .pair-badge {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  min-width: 28px;
  height: 28px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  font-family: var(--sans);
  font-size: 0.85rem;
  font-weight: 700;
  color: #fff;
  background: var(--sim-neutral);
}
.match-item .pair-badge[hidden] {
  display: none;
}

/* Sim ---------------------------------------------------------------------- */

/*
  The other half of the figure/sim distinction — see the note on `.figure`.

  A sim is the thing you CAN touch, so it keeps the frame and takes a heavier,
  warmer one. Two pixels of `--rule-strong` rather than one of `--rule` is a
  small change on its own; what it buys is that the framed thing on the page is
  now always the interactive thing, which is the rule a child can learn once and
  then rely on.
*/
.sim {
  border: 2px solid var(--rule-strong);
  border-radius: var(--r-md);
  background: var(--paper-sunk);
  padding: 12px;
}
.sim-stage {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-sm);
  background: var(--sim-face);
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}
.sim-readouts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin-top: 10px;
  font-family: var(--sans);
  font-size: 0.95rem;
}
.sim-readout {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}
.sim-readout .k {
  color: var(--muted);
  font-size: 0.82rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.sim-readout .v {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.sim-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
}
.sim-caption {
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--muted);
  margin-top: 10px;
}
.sim-hint {
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--muted);
  margin: 0 0 8px;
}

/* Energy / quantity meter. Every bucket gets a bar — see principles.md. */
.meter {
  margin-top: 12px;
}
.meter-title {
  font-family: var(--sans);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}
.meter-row {
  display: grid;
  grid-template-columns: minmax(6.5em, auto) minmax(0, 1fr) 4.5em;
  align-items: center;
  gap: 10px;
  font-family: var(--sans);
  font-size: 0.9rem;
  margin-bottom: 6px;
}
.meter-row .meter-label {
  color: var(--muted);
}
.meter-track {
  height: 16px;
  border-radius: var(--r-pill);
  background: var(--rule);
  overflow: hidden;
}
.meter-fill {
  height: 100%;
  width: 0;
  border-radius: var(--r-pill);
  transition: width 0.16s linear;
}
.meter-row .meter-value {
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.meter-total {
  border-top: 1px dashed var(--rule-strong);
  padding-top: 6px;
  margin-top: 2px;
  font-weight: 700;
}

/* Slider ------------------------------------------------------------------- */

.slider-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 6px;
  padding: 10px 12px;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--paper-sunk);
}
.slider-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-family: var(--sans);
}
.slider-label {
  font-size: 1rem;
  font-weight: 600;
}
.slider-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  border-radius: var(--r-pill);
  padding: 2px 12px;
  min-width: 3.5em;
  text-align: center;
}

/* A 48px-tall row with a 32px thumb. The POC's native range control was ~16px. */
.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--tap);
  margin: 0;
  background: transparent;
  cursor: pointer;
  touch-action: pan-y;
}
.slider::-webkit-slider-runnable-track {
  height: 14px;
  border-radius: var(--r-pill);
  background: var(--rule);
  border: 1px solid var(--rule-strong);
}
.slider::-moz-range-track {
  height: 14px;
  border-radius: var(--r-pill);
  background: var(--rule);
  border: 1px solid var(--rule-strong);
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 32px;
  height: 32px;
  margin-top: -10px;
  border-radius: 50%;
  background: var(--accent);
  border: 3px solid var(--paper);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}
.slider::-moz-range-thumb {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--accent);
  border: 3px solid var(--paper);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}
.slider:active::-webkit-slider-thumb {
  background: var(--accent-ink);
}
.slider:active::-moz-range-thumb {
  background: var(--accent-ink);
}
.slider:focus-visible::-webkit-slider-thumb {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
.slider:focus-visible::-moz-range-thumb {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}
.slider-scale {
  display: flex;
  justify-content: space-between;
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--muted);
}

/* Draw --------------------------------------------------------------------- */

.draw-surface {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  background: var(--paper);
  border: 2px dashed var(--rule-strong);
  border-radius: var(--r-md);
  touch-action: none;
  cursor: crosshair;
}
.draw-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 10px;
}

/* Write -------------------------------------------------------------------- */

.write-area {
  width: 100%;
  min-height: 120px;
  padding: 14px 16px;
  font-family: var(--sans);
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--rule-strong);
  border-radius: var(--r-md);
  resize: vertical;
}
.write-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 10px;
  flex-wrap: wrap;
}
.write-count {
  font-family: var(--sans);
  font-size: 0.85rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.write-count.is-full {
  color: var(--try);
  font-weight: 700;
}

/* Custom (sandboxed iframe) ------------------------------------------------ */

.frame-wrap {
  position: relative;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--paper-sunk);
  overflow: hidden;
}
.lesson-frame {
  display: block;
  width: 100%;
  border: 0;
  background: transparent;
}
.frame-status {
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--muted);
  padding: 14px;
}
/* Questions a sandboxed artifact proposed. They are pills the child may tap — never sent for her. */
.frame-suggests {
  margin-top: 12px;
}
.frame-suggests:empty {
  display: none;
}
.frame-suggests .pill-row {
  margin-top: 6px;
}

.sim-extras:empty {
  display: none;
}
.sim-holder + .sim-caption {
  margin-top: 10px;
}

/* ========================================= suggestion pills (primary input) */

/* The single most important control in the product: one tap sends a question,
   so it is sized like a real button, not like inline text. */
.pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.pill {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  min-height: 44px;
  padding: 10px 18px;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 600;
  line-height: 1.25;
  text-align: left;
  /*
    A pill is a <button> when tapping it sends a message, and an <a> when the page it leads to has
    already been written (see src/lesson/seed-lessons.ts). The two must be indistinguishable: a
    child should not have to know which of her starting questions we happened to write in advance.
    Only the anchor's default underline gives it away, so it is turned off here rather than in a
    separate rule that could drift.
  */
  text-decoration: none;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border: 2px solid var(--accent-line);
  border-radius: var(--r-pill);
  box-shadow: 0 2px 0 var(--accent-line);
  cursor: pointer;
  transition: transform 0.08s ease, background 0.12s ease, color 0.12s ease;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.pill::before {
  content: "\1F4AC";
  font-size: 0.95em;
}
.pill:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.pill:active {
  transform: translateY(2px);
  box-shadow: none;
}
/*
  While the tutor is working every pill is disabled, because a second question
  cannot be worked on until the current turn finishes. Faded and flattened
  rather than hidden: the child should still be able to read the question she
  asked for, and a control that vanishes on tap reads as a crash.

  :disabled beats :hover in the cascade only because it is written after it.
*/
.pill:disabled {
  opacity: 0.5;
  box-shadow: none;
  cursor: default;
  transform: none;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-color: var(--accent-line);
}
.pill--wide {
  width: 100%;
  justify-content: flex-start;
}

.suggest-block {
  margin-top: 26px;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
}
.suggest-title {
  font-family: var(--serif);
  font-size: 1.2rem;
  font-weight: 600;
  margin: 0 0 12px;
}

/* ============================================================ FIX 2 — chat */

.chat {
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  overflow: hidden;
}

/* Wide screens: a sticky right rail that never scrolls out of reach. */
@media (min-width: 901px) {
  .chat {
    position: sticky;
    top: 20px;
    height: calc(100vh - 60px);
    max-height: 860px;
  }
  .chat-toggle {
    cursor: default;
  }
  .chat-toggle .chat-chevron {
    display: none;
  }
}

.chat-toggle {
  -webkit-appearance: none;
  appearance: none;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: var(--tap);
  padding: 12px 16px;
  font-family: var(--sans);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--paper);
  border: 0;
  border-bottom: 1px solid var(--rule);
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}
.chat-toggle .chat-title {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  max-width: 100%;
}
.chat-toggle .chat-name,
.chat-toggle .chat-peek {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-toggle .chat-chevron {
  flex: 0 0 auto;
  font-size: 1rem;
  transition: transform 0.16s ease;
}
.chat-badge {
  flex: 0 0 auto;
  display: none;
  place-items: center;
  min-width: 24px;
  height: 24px;
  padding: 0 7px;
  border-radius: var(--r-pill);
  background: var(--warm);
  color: var(--on-warm);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0;
}
.chat-badge.is-shown {
  display: grid;
}

.chat-log {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding: 12px 14px;
  font-family: var(--sans);
  font-size: 0.98rem;
  line-height: 1.5;
}

.msg {
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 10px 12px;
  margin-bottom: 10px;
  max-width: 100%;
}
.msg .who {
  font-size: 0.7rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  margin-bottom: 3px;
}
/* Render the message that was written, not what HTML reduces it to:
   line breaks and paragraph gaps survive. See principles.md. */
.msg .body {
  white-space: pre-line;
  overflow-wrap: break-word;
}
.msg.me {
  background: var(--accent-soft);
  border-color: var(--accent-line);
  color: var(--accent-ink);
}
.msg.pending {
  opacity: 0.9;
  font-style: italic;
}
.msg.pending .body::after {
  content: "";
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-left: 3px;
  vertical-align: baseline;
  border-radius: 50%;
  background: var(--muted);
  animation: pulse-dot 1.1s infinite;
}
@keyframes pulse-dot {
  0%,
  100% {
    opacity: 0.2;
  }
  50% {
    opacity: 1;
  }
}
.msg .cta {
  margin-top: 10px;
}

.chat-suggests {
  padding: 0 14px 10px;
}
.chat-suggests:empty {
  display: none;
}
.chat-suggests .pill-row {
  gap: 8px;
}
.chat-suggests .pill {
  font-size: 0.95rem;
  padding: 9px 14px;
}

.chat-input {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  padding: 12px 14px;
  border-top: 1px solid var(--rule);
  background: var(--paper);
  font-family: var(--sans);
}
.chat-input textarea {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--tap);
  max-height: 160px;
  resize: none;
  font-family: var(--sans);
  /* 16px minimum, or iOS Safari zooms the whole page on focus. */
  font-size: 16px;
  line-height: 1.4;
  padding: 12px 14px;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--rule-strong);
  border-radius: var(--r-md);
}
.chat-send {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  min-height: var(--tap);
  min-width: var(--tap);
  padding: 12px 18px;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 700;
  color: var(--on-accent);
  background: var(--accent);
  border: 2px solid var(--accent);
  border-radius: var(--r-md);
  box-shadow: 0 2px 0 var(--accent-ink);
  cursor: pointer;
  touch-action: manipulation;
}
.chat-send:hover {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
}
.chat-send:active {
  transform: translateY(2px);
  box-shadow: none;
}
/*
  Reads "Working…" while a turn is in flight. This is the only progress signal
  that is on screen at every width — the pending bubble is inside a dock that is
  collapsed below 900px, and above it the home page hides the dock's suggestion
  row — so it carries the weight when nothing else is visible.
*/
.chat-send:disabled {
  opacity: 0.7;
  box-shadow: none;
  cursor: progress;
  transform: none;
  background: var(--accent-ink);
  border-color: var(--accent-ink);
}

/* --------------------------------------------------- narrow: bottom-docked */

/*
  The POC's failure: at 820px the grid collapsed and the chat panel landed
  below the whole lesson, roughly 1000px of scrolling from the child's thumb.
  Here it leaves the document flow entirely and docks to the bottom edge.
*/
@media (max-width: 900px) {
  .app {
    grid-template-columns: minmax(0, 1fr);
    gap: 18px;
    padding: 16px 14px calc(var(--dock-h) + 24px);
  }

  .app-chat {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    padding: 0;
  }

  .chat {
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    border-bottom: 0;
    box-shadow: 0 -6px 28px rgba(40, 26, 6, 0.18);
    max-height: var(--dock-h);
    height: var(--dock-h);
    transition: height 0.22s ease, max-height 0.22s ease;
  }
  .chat[data-expanded="true"] {
    height: min(78vh, 560px);
    max-height: min(78vh, 560px);
  }

  /* Collapsed: only the tap bar is reachable, and it is a full-width 56px
     target with an unread count. Nothing below it can be scrolled past. */
  .chat-log,
  .chat-input,
  .chat-suggests {
    display: none;
  }
  .chat[data-expanded="true"] .chat-log {
    display: block;
  }
  .chat[data-expanded="true"] .chat-input {
    display: flex;
  }
  .chat[data-expanded="true"] .chat-suggests {
    display: block;
  }

  .chat-toggle {
    min-height: var(--dock-h);
    font-size: 0.9rem;
    text-transform: none;
    letter-spacing: 0;
    color: var(--ink);
    padding: 14px 18px;
  }
  .chat-toggle .chat-chevron {
    transform: rotate(180deg);
  }
  .chat[data-expanded="true"] .chat-toggle .chat-chevron {
    transform: rotate(0deg);
  }
  .chat-toggle .chat-peek {
    display: block;
    font-weight: 400;
    font-size: 0.85rem;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .lesson {
    padding: 18px 16px 24px;
  }
  .match-grid {
    gap: 10px;
  }
  .meter-row {
    grid-template-columns: minmax(5.5em, auto) minmax(0, 1fr) 4em;
  }
}

@media (min-width: 901px) {
  .chat-toggle .chat-peek {
    display: none;
  }
}

/* Very narrow: stack the match columns so targets stay wide enough to hit. */
@media (max-width: 480px) {
  .match-grid {
    grid-template-columns: minmax(0, 1fr);
  }
  .match-col-wrap + .match-col-wrap {
    margin-top: 12px;
  }
  .pill {
    width: 100%;
    justify-content: flex-start;
  }
}

/* --------------------------------------------------------------- home page */

.lesson-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.lesson-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 88px;
  padding: 16px;
  background: var(--paper);
  border: 2px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: 0 2px 0 var(--rule-strong);
  text-decoration: none;
  color: var(--ink);
  font-family: var(--sans);
}
.lesson-card:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.lesson-card .t {
  font-family: var(--serif);
  font-size: 1.15rem;
  font-weight: 600;
}
.lesson-card .m {
  font-size: 0.8rem;
  color: var(--muted);
}

.hero {
  background: linear-gradient(135deg, var(--warm-soft) 0%, var(--accent-soft) 100%);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 18px 22px;
  margin-bottom: 20px;
}
.hero p {
  margin: 0.25rem 0 0;
  color: var(--muted);
}

.attribution {
  font-family: var(--sans);
  font-size: 0.75rem;
  color: var(--muted);
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 20px 28px;
}

/* -------------------------------------------------- a page being written -- */

/*
 * The card for a page that has not finished writing itself.
 *
 * Muted rather than loud: it is a statement of fact for a child choosing between cards, not a
 * warning. Without it, a page she opened seconds ago is either missing from her list or looks
 * identical to a finished one and then turns out to be half empty.
 */
.lesson-card-writing {
  display: block;
  margin-top: 4px;
  color: var(--muted);
  font-size: 0.85rem;
}


/*
 * Progressive lesson generation. A page opens in under a second with a title and an outline, then
 * fills in beneath the child as the author streams it.
 *
 * Everything here is scaffolding and every rule in it is temporary by design: the client removes
 * these elements on publication, so a finished lesson is exactly its published spec. Nothing below
 * may change the layout of the blocks themselves, or the page would visibly reflow at the moment it
 * completes.
 */

.lesson-outline-box {
  margin: 0 0 1.5rem;
  padding: 16px 20px;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
}

.lesson-intro {
  margin: 0 0 1rem;
  color: var(--muted);
}

.lesson-outline-title {
  margin: 0 0 0.5rem;
  font-size: 1rem;
  color: var(--muted);
}

.lesson-outline-list {
  margin: 0;
  padding-left: 1.2em;
}

.lesson-outline-item {
  margin: 0.25rem 0;
}

/* Empty containers the renderer always creates, so they must not take up space when unused. */
.lesson-outline:empty,
.lesson-writing:empty,
.lesson-suggest:empty {
  display: none;
}

.lesson-writing-note {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 1.25rem 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.lesson-writing-text {
  min-width: 0;
}

/*
 * One quiet pulse. The point is only to distinguish "still arriving" from "finished and this is
 * all there is" — a child who cannot tell those apart will sit waiting for a page that is done.
 */
.lesson-writing-dot {
  flex: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  animation: lesson-writing-pulse 1.4s ease-in-out infinite;
}

@keyframes lesson-writing-pulse {
  0%,
  100% {
    opacity: 0.25;
  }
  50% {
    opacity: 1;
  }
}

/*
 * Questions are inert until the page is published.
 *
 * Not a visual choice: `applyAnswer` derives a block's topic id from the PUBLISHED spec, so an
 * answer given before publication records no evidence at all. Rather than accept a silent hole in
 * "mastery advances only on observed evidence", the question is not answerable yet — and it has to
 * LOOK unavailable, because a control that takes a tap and does nothing is the exact failure
 * `interact_miss` exists to catch.
 */
/*
 * Only the CONTROLS are dimmed, never the question.
 *
 * Dimming the whole block took the prompt down with it, and the prompt is lesson content: she should
 * be able to read what is being asked while she waits, even though she cannot answer it yet. WCAG
 * exempts inactive controls from contrast requirements; it does not exempt the text beside them.
 */
.is-writing .choice-list,
.is-writing .order-list,
.is-writing .match-grid {
  opacity: 0.55;
}

.is-writing .choice-list button,
.is-writing .order-list button,
.is-writing .match-grid button {
  cursor: default;
}

/*
 * Placeholders: the shape of a block that has not arrived yet.
 *
 * Stage two of staged generation. Before these existed the page grew from the bottom one block at a
 * time, so everything a child was reading moved down the screen each time something landed, and a
 * page still filling looked exactly like one that had stopped. A placeholder holds the position, so
 * the page reaches its finished length in about a second and then fills in place.
 *
 * **Reserve space, never reclaim it.** Every `min-height` below is deliberately a little SHORTER
 * than the block it stands in for. A figure's aspect ratio is unknowable until its picture arrives,
 * so some settling is unavoidable — erring short means the swap always pushes content DOWN, never
 * pulls it up out from under someone mid-sentence. A placeholder taller than its block would be
 * worse than none.
 */

.lesson-ph {
  display: block;
}

/* Wide things: a figure, a simulation, a sandbox-built widget, somewhere to draw. */
.lesson-ph-box {
  min-height: 190px;
}

/* A prompt over a stack of options. */
.lesson-ph-quiz {
  min-height: 130px;
}

/* Prose. Three lines, the shortest thing here, because text blocks are capped at 400 characters. */
.lesson-ph-lines {
  min-height: 64px;
}

.lesson-ph-frame {
  display: block;
  width: 100%;
  /* Percentage padding is relative to WIDTH, so this is a 16:9 box that scales down with the column
     rather than overflowing it at 375px. */
  padding-top: 52%;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: var(--paper-sunk);
}

.lesson-ph-bar {
  display: block;
  height: 0.85em;
  margin: 0 0 0.5em;
  border-radius: 4px;
  background: var(--paper-sunk);
}

.lesson-ph-bar-full {
  width: 100%;
  max-width: 62ch;
}

/* The last line of a paragraph is rarely full width; matching that keeps it reading as prose. */
.lesson-ph-bar-short {
  width: 62%;
  max-width: 38ch;
}

.lesson-ph-options {
  display: block;
  margin-top: 0.75em;
}

.lesson-ph-options .lesson-ph-bar {
  height: 2.1em;
  border: 1px solid var(--rule);
  border-radius: var(--r-md, 10px);
}

.lesson-ph-note {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0.6rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
  /* A caption can be a full sentence the author wrote; it must wrap, not push the column wider. */
  min-width: 0;
}

.lesson-ph-dot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
  animation: lesson-ph-pulse 1.4s ease-in-out infinite;
}

/*
 * One shimmer, shared. Moves across the reserved bars so "still arriving" is distinguishable from
 * "finished and this is all there is" without any claim about how long it will take.
 */
.lesson-ph-bar,
.lesson-ph-frame {
  background-image: linear-gradient(
    90deg,
    transparent 0%,
    rgb(255 255 255 / 0.65) 50%,
    transparent 100%
  );
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: lesson-ph-sweep 1.6s ease-in-out infinite;
}

@keyframes lesson-ph-sweep {
  from {
    background-position: 150% 0;
  }
  to {
    background-position: -50% 0;
  }
}

@keyframes lesson-ph-pulse {
  0%,
  100% {
    opacity: 0.25;
  }
  50% {
    opacity: 1;
  }
}

/* -------------------------------------------------- reduced motion, last -- */


@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  /* Decorative only. The simulations keep the motion that *is* the lesson;
     they read the same media query in JS and drop easing, not physics. */
  .msg.pending .body::after {
    animation: none;
    opacity: 0.6;
  }
  /* Same reasoning: the dot must still read as "not finished", without moving. */
  .lesson-writing-dot,
  .lesson-ph-dot {
    animation: none;
    opacity: 0.6;
  }
  /* A placeholder still has to read as a reserved space, so it keeps its shape and loses only the
     sweep. The caption beside it says what is being written, which is the part that carries the
     meaning anyway — and reaches a reduced-motion reader unmodified. */
  .lesson-ph-bar,
  .lesson-ph-frame {
    animation: none;
    background-image: none;
  }
}

@media (prefers-contrast: more) {
  .lesson-btn,
  .choice-option,
  .order-item,
  .match-item,
  .pill {
    border-width: 3px;
  }
}

@media print {
  .app-chat,
  .sim-controls,
  .draw-tools {
    display: none;
  }
  .app {
    grid-template-columns: 1fr;
  }
}

/* ============================================================================
   PARENT-FACING UI
   ============================================================================

   Everything below styles the server-rendered pages in src/ui/ — the auth
   screens, the parent shell, the children list, the settings page and the
   progress dashboard. None of it existed: the stylesheet was written for the
   child-facing lesson renderer, and the parent pages shipped as bare HTML.

   Same tokens as the rest of the file, so dark mode comes free. The child's
   side stays warm and playful; the parent's side is the same paper, a little
   quieter and denser, because a dashboard is for reading.
   ============================================================================ */

/* --------------------------------------------------------------- shell --- */

body.parent {
  font-family: var(--sans);
  font-size: 16px;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 0 24px;
  min-height: 64px;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  position: sticky;
  top: 0;
  z-index: 20;
}

.brand {
  font-family: var(--serif);
  font-size: 1.3rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.brand:hover {
  color: var(--accent-ink);
}

.nav {
  display: flex;
  gap: 4px;
  flex: 1;
  min-width: 0;
}

.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 14px;
  border-radius: var(--r-pill);
  color: var(--muted);
  text-decoration: none;
  font-size: 0.95rem;
  white-space: nowrap;
}
.nav-link:hover {
  background: var(--paper-sunk);
  color: var(--ink);
}
/* The current section is marked by weight and a filled pill, not colour alone. */
.nav-link.is-active {
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}

.topbar-end {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
}
.who {
  color: var(--muted);
  font-size: 0.9rem;
  white-space: nowrap;
}

.page {
  max-width: 1080px;
  margin: 0 auto;
  padding: 28px 24px 56px;
}
.page > h1 {
  font-size: 1.9rem;
  margin: 0 0 4px;
}

.footer {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 24px 40px;
  color: var(--muted);
  font-size: 0.78rem;
  line-height: 1.6;
}
.footer p {
  margin: 0;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
}

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

/*
  The parent-side button. Deliberately smaller than .lesson-btn — a child needs
  a 48px target for an imprecise tap, an adult on a laptop does not, and forcing
  lesson-sized controls into a dense settings form makes it harder to scan.
  Still 40px, which is above the practical floor for a mouse.
*/
.btn {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  min-height: 40px;
  padding: 8px 16px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 0.92rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}
.btn:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.btn:active {
  transform: translateY(1px);
}

.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}
.btn-primary:hover {
  background: var(--accent-ink);
  border-color: var(--accent-ink);
  color: var(--on-accent);
}

.btn-quiet {
  border-color: transparent;
  background: transparent;
  color: var(--muted);
  font-weight: 500;
}
.btn-quiet:hover {
  background: var(--paper-sunk);
  color: var(--ink);
  border-color: var(--rule);
}

/* -------------------------------------------------------------- banners --- */

.banner {
  max-width: 1080px;
  margin: 16px auto 0;
  padding: 12px 16px;
  border: 1px solid var(--rule-strong);
  border-left-width: 4px;
  border-radius: var(--r-sm);
  background: var(--paper);
  font-size: 0.9rem;
  line-height: 1.5;
}
.banner p {
  margin: 0 0 8px;
}
.banner p:last-child {
  margin-bottom: 0;
}
.banner-info {
  border-left-color: var(--warm);
  background: var(--warm-soft);
}
.banner-error {
  border-left-color: var(--try);
  background: var(--try-soft);
  color: var(--try);
  font-weight: 500;
}

/* ----------------------------------------------------------------- auth --- */

.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 32px 20px;
}
.auth-card {
  width: 100%;
  max-width: 420px;
  padding: 32px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
}
.auth-card h1 {
  margin: 0 0 4px;
  font-size: 1.6rem;
}
/*
  Direct child only. As a descendant selector this also centred the per-field
  hints inside labels ("At least 10 characters."), which should sit under their
  input at the left edge like the label above them.
*/
.auth-card > .hint {
  margin-top: 20px;
  text-align: center;
}
.auth-card .banner {
  margin: 0 0 18px;
  max-width: none;
}

/* A plain vertical form. Labels own their inputs, so the whole row is a target. */
.stack {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 20px;
}
/*
  Block layout, deliberately not flex-column.
  A flex label turns every child into its own row, so an inline qualifier like
  `<span class="optional">(optional)</span>` dropped onto a second line, made
  that grid cell taller than its neighbours, and pushed its input out of line
  with the rest of the row. As blocks, inline spans stay inline and every label
  is one line, so the inputs align without any bottom-alignment trickery.
*/
.stack label,
.grid-form label {
  display: block;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--ink);
}
.stack label > input,
.stack label > textarea,
.stack label > select,
.grid-form label > input,
.grid-form label > textarea,
.grid-form label > select {
  display: block;
  margin-top: 6px;
}
.stack .hint,
.grid-form .hint {
  display: block;
  font-weight: 400;
  margin: 6px 0 0;
}

/* ---------------------------------------------------------------- forms --- */

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
select,
textarea:not(.write-area):not(.chat-text) {
  font: inherit;
  font-family: var(--sans);
  font-size: 1rem;
  min-height: 44px;
  padding: 10px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--paper);
  color: var(--ink);
  width: 100%;
}
/*
  A select is the one control that ignores the shared rule above without help. Browsers give it a
  UA-supplied appearance that overrides font, height and background, so it rendered two-thirds the
  height of the inputs beside it and in the system font. Resetting `appearance` hands the box back,
  which then needs its own arrow — an inline SVG rather than a font glyph, because a glyph inherits
  `color` and vanishes at the low-contrast end of the dark theme.
*/
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 34px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23767676' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 10px 6px;
  cursor: pointer;
}
select:hover {
  border-color: var(--muted);
}
textarea:not(.write-area):not(.chat-text) {
  min-height: 88px;
  resize: vertical;
  line-height: 1.5;
}
input:hover,
textarea:hover {
  border-color: var(--muted);
}

.grid-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 16px;
  align-items: start;
  margin-top: 16px;
}
/* Free-text notes are the main personalisation input, so they get the full width. */
.grid-form .wide {
  grid-column: 1 / -1;
}
.grid-form button {
  justify-self: start;
  align-self: end;
}

.row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
}
.row .grow {
  flex: 1;
  min-width: 200px;
}

.optional {
  font-weight: 400;
  color: var(--muted);
}

/* ----------------------------------------------------------------- text --- */

.lede {
  color: var(--muted);
  font-size: 1rem;
  margin: 0 0 20px;
}
.muted {
  color: var(--muted);
}
.meta {
  color: var(--muted);
  font-size: 0.85rem;
  margin: 2px 0 10px;
}
.notes {
  color: var(--ink);
  font-size: 0.9rem;
  line-height: 1.5;
  margin: 0 0 14px;
}
.empty {
  color: var(--muted);
  padding: 20px;
  background: var(--paper-sunk);
  border: 1px dashed var(--rule-strong);
  border-radius: var(--r-md);
  text-align: center;
}
.plain {
  list-style: none;
  margin: 0;
  padding: 0;
}
.plain li {
  padding: 7px 0;
  border-bottom: 1px solid var(--rule);
  font-size: 0.92rem;
}
.plain li:last-child {
  border-bottom: 0;
}
.stat {
  margin: 0 0 8px;
  font-size: 0.95rem;
}
.code-block {
  display: block;
  padding: 10px 12px;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.82rem;
  word-break: break-all;
}

/* Alias for the existing .visually-hidden, so both names work. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------- layout --- */

.page section.card,
.page .card {
  padding: 22px 24px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  margin-bottom: 20px;
}
.page .card > h2 {
  margin: 0 0 14px;
  font-size: 1.15rem;
}

.two-col {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 20px;
}
.two-col > .card {
  margin-bottom: 0;
}

/* ------------------------------------------------------------- children --- */

.child-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 20px;
  margin: 0 0 24px;
  padding: 0;
  list-style: none;
}
.child-card {
  display: flex;
  flex-direction: column;
  margin-bottom: 0;
}
.child-card h3 {
  margin: 0;
  font-size: 1.25rem;
}
/* Push the actions to the bottom so cards of different note lengths line up. */
.child-card .row {
  margin-top: auto;
  padding-top: 4px;
}

/* Removed children. Quieter than an active card — present, but not competing with them. */
.archived {
  background: var(--paper-sunk);
}
.archived summary {
  cursor: pointer;
}
/* The heading sits inside <summary> for the disclosure triangle, so undo its block display. */
.archived summary h2 {
  display: inline;
  margin: 0;
}
.archived-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  border-top: 1px solid var(--rule);
}
.archived-row .meta {
  margin: 2px 0 0;
}

/* -------------------------------------------------------------- picker --- */

.picker {
  max-width: 720px;
  margin: 0 auto;
  padding: 48px 24px;
  text-align: center;
}
.picker h1 {
  margin-bottom: 28px;
}
.picker-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  margin: 0 0 28px;
  padding: 0;
  list-style: none;
}
/* Big, obvious, and reachable by a child who is choosing their own page. */
.picker-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 168px;
  padding: 24px 16px;
  background: var(--paper);
  border: 2px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: 0 2px 0 var(--rule-strong);
  text-decoration: none;
  color: var(--ink);
}
.picker-card:hover {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--accent-ink);
}
.picker-avatar {
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: var(--accent-soft);
  border: 2px solid var(--accent-line);
  color: var(--accent-ink);
  font-family: var(--serif);
  font-size: 1.6rem;
  font-weight: 600;
}
.picker-name {
  font-family: var(--serif);
  font-size: 1.25rem;
  font-weight: 600;
}
.picker-grade {
  color: var(--muted);
  font-size: 0.85rem;
}

/* ------------------------------------------------------------ dashboard --- */

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  margin: 20px 0 24px;
}
.stat-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px 18px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  box-shadow: var(--shadow);
}
.stat-value {
  font-family: var(--serif);
  font-size: 1.9rem;
  font-weight: 600;
  line-height: 1.1;
}
.stat-label {
  color: var(--muted);
  font-size: 0.8rem;
}

/*
  Mastery bar. Four segments, but colour is never the only cue — the legend
  below repeats every value as a number, so it reads the same to someone who
  cannot separate the greens.
*/
.bar-stack {
  display: flex;
  height: 18px;
  border-radius: var(--r-pill);
  overflow: hidden;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
}
.bar {
  min-width: 3px;
}
.bar-mastered {
  background: var(--accent-ink);
}
.bar-solid {
  background: var(--accent);
}
.bar-working {
  background: var(--accent-line);
}
.bar-introduced {
  background: var(--rule-strong);
}

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
}
.legend li {
  display: flex;
  align-items: center;
  gap: 7px;
}
.swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  border: 1px solid rgba(0, 0, 0, 0.12);
}
.swatch-mastered {
  background: var(--accent-ink);
}
.swatch-solid {
  background: var(--accent);
}
.swatch-working {
  background: var(--accent-line);
}
.swatch-introduced {
  background: var(--rule-strong);
}

/* Status pill in the concepts table. */
.chip {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  border: 1px solid transparent;
}
.chip-mastered {
  background: var(--accent-ink);
  color: var(--on-accent);
}
.chip-solid {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.chip-working {
  background: var(--warm-soft);
  border-color: var(--warm);
  color: var(--try);
}
.chip-introduced {
  background: var(--paper-sunk);
  border-color: var(--rule-strong);
  color: var(--muted);
}
/* A named misconception. Reads as a diagnosis, not a failure. */
.chip-warn {
  background: var(--try-soft);
  border-color: var(--try-line);
  color: var(--try);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.74rem;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.table th,
.table td {
  padding: 9px 12px;
  text-align: left;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
.table thead th {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  font-weight: 600;
  border-bottom: 2px solid var(--rule-strong);
  white-space: nowrap;
}
.table tbody tr:last-child td {
  border-bottom: 0;
}
.table tbody tr:hover {
  background: var(--paper-sunk);
}
.table tbody th {
  font-weight: 600;
  white-space: nowrap;
}
/*
  Horizontal scroll for wide tables is a NARROW-SCREEN measure only.
  Setting display:block takes a table out of table layout, so width:100% stops
  applying and the table shrinks to fit its content — on a desktop card that
  reads as a broken half-width table.
*/
@media (max-width: 720px) {
  .card > .table {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
  }
  .card > .table td.muted {
    white-space: normal;
    min-width: 200px;
  }
}

.spark {
  color: var(--accent);
  line-height: 0;
}

/* Subject label. Matches .subject-tag so the two sides of the app agree. */
.tag {
  display: inline-block;
  padding: 2px 9px;
  border-radius: var(--r-pill);
  background: var(--paper-sunk);
  border: 1px solid var(--rule-strong);
  color: var(--muted);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: capitalize;
  white-space: nowrap;
}

/*
  Access status on the admin page. Variants of .tag rather than a parallel badge system, because
  .pill is an interactive chat chip and would carry its hover and press states onto a label.
  Colour is a token in every case, so the dark palette flips these with everything else.
*/
.tag-approved {
  background: var(--good-soft);
  border-color: var(--good-line);
  color: var(--good);
}
.tag-pending {
  background: var(--warm-soft);
  border-color: var(--try-line);
  color: var(--warm-dark);
}
.tag-denied {
  background: var(--try-soft);
  border-color: var(--try-line);
  color: var(--try);
}

/* Spend meter on the settings page. */
.meter[role="img"] {
  height: 10px;
  margin: 10px 0;
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.meter[role="img"] .meter-fill {
  height: 100%;
  background: var(--accent);
}

/* ------------------------------------------------------- child home page --- */

.child-header {
  margin-bottom: 24px;
}
.child-header h1 {
  margin: 0 0 4px;
  font-size: 2rem;
}

.section {
  margin-bottom: 28px;
}
.section > h2 {
  font-size: 1.15rem;
  margin: 0 0 12px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--rule);
}

.pill-list {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.pill-list li {
  display: contents;
}
/* Static pills (dashboard "what holds her attention") are labels, not buttons. */
.pill-list.static .pill {
  cursor: default;
}

/*
  On the home page the starting questions are already the main content, so the
  copy the chat panel keeps in its dock is redundant on a wide screen. It stays
  on narrow screens, where the originals have scrolled away and the dock is
  where the child's thumb already is.
*/
@media (min-width: 901px) {
  .app:has(.app-main .pill-list) .chat-suggests {
    display: none;
  }
}

/* --------------------------------------------------------------- narrow --- */

@media (max-width: 720px) {
  .topbar {
    flex-wrap: wrap;
    gap: 8px 14px;
    padding: 10px 16px;
  }
  .nav {
    order: 3;
    /*
      `flex-basis: 100%` rather than `width: 100%`. The base rule sets `flex: 1`,
      whose `flex-basis: 0%` beats `width` for a flex item — so the nav stayed on
      the brand's row and scrolled sideways next to "Sign out" instead of
      wrapping onto its own line.
    */
    flex: 0 0 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .nav::-webkit-scrollbar {
    display: none;
  }
  .topbar-end {
    margin-left: auto;
  }
  .who {
    display: none;
  }
  .page,
  .footer {
    padding-left: 16px;
    padding-right: 16px;
  }
  .page > h1 {
    font-size: 1.5rem;
  }
  .banner {
    margin-left: 16px;
    margin-right: 16px;
  }
  .stat-row {
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  }
  .stat-value {
    font-size: 1.5rem;
  }
  .picker-card {
    width: 140px;
  }
}

/* ------------------------------------------------- parent UI refinements --- */

/*
  Three actions never fit on one line in a card this width, so let the third
  wrap deliberately and sit to the right rather than dangling under the first.
*/
.child-card .row {
  gap: 8px;
}
.child-card .row .btn-quiet {
  margin-left: auto;
}

/* Cards in a row should end level, whether or not the title wraps to two lines. */
.lesson-cards > li {
  display: flex;
}
.lesson-cards > li > .lesson-card {
  flex: 1;
}

/*
  The child's greeting doubles as the page's anchor, so it uses the warm .hero
  panel rather than a bare heading — the home page is otherwise very short and
  read as unfinished next to the full-height chat rail.
*/
.child-header.hero h1 {
  margin: 0 0 6px;
  font-size: 2rem;
}
.child-header.hero p {
  color: var(--ink);
  opacity: 0.75;
}

/* ------------------------------------------------------- child sign-in --- */

/*
  The child's own sign-in page. Same card as the parent form, scaled up: a
  seven-year-old is aiming at these with a finger, and the two fields are the
  only thing on the page, so there is room to make them generous.
*/
.kids-card {
  max-width: 460px;
  text-align: center;
}
.kids-card h1 {
  font-size: 2rem;
}
.kids-card .stack {
  text-align: left;
}
/*
  The `input` qualifier is load-bearing, not decoration. The base form rules
  select `input[type="text"]` and `input[type="password"]`, which outrank a bare
  `.big-input` — so as a plain class this silently rendered at the ordinary 44px
  and 1rem, and the whole point of the rule was lost with nothing to show for it.
*/
input.big-input {
  min-height: 52px;
  font-size: 1.25rem;
  letter-spacing: 0.02em;
}

/*
  Sits in the corner of the child's greeting panel. A child signed in on her own
  needs a way out of a shared tablet; a parent looking over her shoulder needs a
  way back to their account.

  A grid column rather than absolute positioning. Absolute looked right for the
  short "Sign out" case and then failed twice: the parent variant is more than
  twice as wide ("Back to Sam's account"), and a long child name grows the
  greeting rightwards until it runs underneath the control. Measured at 620px
  there were about 40 spare pixels — enough to pass a glance, not enough to trust
  a name to. A column cannot overlap its neighbour at any width or name length.

  On a narrow screen it collapses to a single column and follows the greeting.
*/
.child-header.hero {
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: 16px;
  align-items: start;
}
.child-header.hero > h1,
.child-header.hero > p:not(.child-signout) {
  grid-column: 1;
}
.child-signout {
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  margin: 0;
}
@media (max-width: 560px) {
  .child-header.hero {
    grid-template-columns: 1fr;
  }
  .child-signout {
    grid-column: 1;
    grid-row: auto;
    justify-self: start;
    margin-top: 12px;
  }
}

/*
  A login code gets read aloud and copied by hand, so it is set in the mono face
  at a size that survives being squinted at across a kitchen table.
*/
.login-code {
  /* Same stack as .code-block — there is no --mono custom property. */
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 1.05em;
  padding: 2px 8px;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--paper-sunk);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* Revoking a child's login is destructive enough to look different from Save. */
.btn-danger {
  border-color: var(--try-line);
  color: var(--try);
}
.btn-danger:hover {
  background: var(--try-soft);
  border-color: var(--try);
  color: var(--try);
}

/* ==========================================================================
   Landing page (public, signed out)
   ==========================================================================

   The "exercise book" direction: the app's existing paper-and-ink palette with
   two pieces of workbook furniture on top — faint blue horizontal rules and a
   red vertical margin line — and the child's own voice set in --hand.

   Everything is namespaced `home-` and lives on `body.home`, so none of it can
   reach the signed-in surfaces. Colours come from the shared tokens rather than
   literals, which is what makes the page correct in dark mode for free; the
   simulation uses the same --sim-* tokens the live lesson renderer does.
   ========================================================================== */

.home {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
}

.home-main {
  display: block;
}

/* ------------------------------------------------------------------ nav --- */

.home-nav {
  display: flex;
  align-items: center;
  gap: 24px;
  flex-wrap: wrap;
  padding: 18px clamp(20px, 5vw, 56px);
  border-bottom: 1px solid var(--rule);
  background: var(--bg);
}

.home-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-right: auto;
  font-family: var(--display);
  font-size: 1.3rem;
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
}

.home-brand-mark {
  flex: none;
}
.home-brand-page {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.6;
}
.home-brand-margin {
  stroke: var(--margin-line);
  stroke-width: 1.2;
}
.home-brand-rule {
  stroke: var(--paper-rule);
  stroke-width: 1.4;
}

.home-nav-links {
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
}

.home-nav-link {
  font-size: 0.94rem;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
}
.home-nav-link:hover {
  color: var(--ink);
}

/* --------------------------------------------------------------- calls --- */

/*
  Orange, not the green .btn-primary the signed-in surfaces use. The palette's
  documented meaning is "orange = go here next", and on this page there is
  exactly one thing to do next.
*/
.home-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 13px 24px;
  border: 1px solid var(--warm);
  border-radius: var(--r-sm);
  background: var(--warm);
  color: var(--on-warm);
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
}
.home-cta:hover {
  background: var(--warm-dark);
  border-color: var(--warm-dark);
}

.home-cta-small {
  min-height: 40px;
  padding: 9px 17px;
  font-size: 0.92rem;
}

.home-cta-quiet {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 13px 22px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  color: var(--ink);
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
}
.home-cta-quiet:hover {
  border-color: var(--accent);
  color: var(--accent-ink);
}

.home-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
}

.home-fineprint {
  margin: 0;
  font-size: 0.88rem;
  color: var(--muted);
}

/* ----------------------------------------------------------------- hero --- */

/*
  The red margin rule runs down the left of every full-width section. It is drawn
  as a positioned 1px column rather than a background image so it lands on an
  exact pixel and can be removed cleanly on narrow screens.
*/
.home-hero,
.home-steps,
.home-ladder {
  position: relative;
  padding-left: clamp(20px, 7vw, 96px);
  padding-right: clamp(20px, 5vw, 56px);
}

.home-hero::before,
.home-steps::before,
.home-ladder::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: clamp(12px, 4.5vw, 64px);
  width: 1px;
  background: var(--margin-line);
  opacity: 0.5;
}

.home-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 470px);
  gap: clamp(28px, 5vw, 56px);
  align-items: center;
  padding-top: clamp(40px, 6vw, 62px);
  padding-bottom: clamp(44px, 6vw, 70px);
}

/* Grid children default to min-width:auto, which lets a long unbroken word push
   the column wider than its track and take the demo card off screen with it. */
.home-hero-copy {
  min-width: 0;
}

.home-ask {
  margin: 0 0 24px;
}

.home-ask-who {
  display: block;
  font-family: var(--hand);
  font-size: 1.15rem;
  color: var(--muted);
}

.home-ask-q {
  display: block;
  font-family: var(--hand);
  font-size: clamp(1.7rem, 4vw, 2.2rem);
  line-height: 1.15;
  color: var(--accent);
}

.home-title {
  font-family: var(--display);
  font-size: clamp(2.2rem, 6vw, 3.65rem);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.02em;
  margin: 0 0 18px;
  text-wrap: balance;
}

.home-lede {
  font-size: 1.12rem;
  line-height: 1.62;
  color: var(--muted);
  margin: 0 0 26px;
  max-width: 32em;
}
.home-lede strong {
  color: var(--ink);
  font-weight: 600;
}

/* ---------------------------------------------------------- demo lesson --- */

.home-demo {
  margin: 0;
  padding: 22px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lift);
  transform: rotate(-0.7deg);
}

.home-demo-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 0.78rem;
  color: var(--muted);
  margin-bottom: 14px;
}

.home-demo-chip {
  padding: 3px 9px;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-weight: 600;
}

.home-demo-title {
  font-family: var(--display);
  font-size: 1.3rem;
  font-weight: 600;
  margin: 0 0 14px;
}

/*
  Holds either the static picture or the live sim that replaces it, so it carries no chrome of its
  own — each child brings its own. `.sim` already has a border, a radius and a background, and a
  container supplying a second set produced a frame inside a frame.
*/
.home-stage {
  margin-bottom: 14px;
}
.home-stage > .sim {
  margin: 0;
}

/* The static picture, which is the no-JavaScript fallback for the block above. */
.home-sim {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  background: var(--sim-face);
}

/*
  The live sim states its own affordance ("Drag the box to push it, then let go"). Inside this card
  that line is the child's voice from the hero, so it is set in the same hand — which also makes it
  read as part of the design rather than as instructions bolted on top.
*/
.home-demo .sim-hint {
  font-family: var(--hand);
  font-size: 1.15rem;
  color: var(--accent);
}

/* The real range input, once the demo goes live, filling the row the drawn bar occupied. */
.home-control .slider {
  flex: 1;
  min-width: 0;
}
.home-sim-ground {
  stroke: var(--sim-ground);
  stroke-width: 2.5;
}
.home-sim-hatch {
  stroke: var(--sim-ground);
  stroke-width: 1.6;
  opacity: 0.55;
}
.home-sim-box {
  fill: var(--sim-object);
  stroke: var(--sim-object-line);
  stroke-width: 2;
}
.home-sim-push {
  stroke: var(--accent);
  stroke-width: 3;
  fill: none;
}
.home-sim-friction {
  stroke: var(--sim-hot);
  stroke-width: 2.5;
  fill: none;
}
.home-sim-text {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 600;
}
.home-sim-text-push {
  fill: var(--accent);
}
.home-sim-text-friction {
  fill: var(--sim-hot);
}

.home-control {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 16px;
}

.home-control-label {
  font-size: 0.82rem;
  color: var(--muted);
}

.home-track {
  position: relative;
  flex: 1;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
}

.home-track-fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: 62%;
  border-radius: var(--r-pill);
  background: var(--accent);
}

.home-knob {
  position: absolute;
  left: 62%;
  top: 50%;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  background: var(--paper);
  border: 2px solid var(--accent);
  box-shadow: var(--shadow);
}

.home-q {
  font-size: 1rem;
  font-weight: 600;
  margin: 0 0 10px;
}

.home-choices {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
}

.home-choice {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border: 1.5px solid var(--rule-strong);
  border-radius: var(--r-md);
  background: var(--paper);
  font-size: 0.94rem;
}

.home-choice-correct {
  border-color: var(--good-line);
  background: var(--good-soft);
  color: var(--good);
  font-weight: 600;
}

.home-bullet {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1.5px solid var(--rule-strong);
  font-size: 0.75rem;
}
.home-choice-correct .home-bullet {
  border-color: var(--good);
  background: var(--good);
  color: var(--paper);
}

/* ------------------------------------------------------------- sections --- */

.home-section {
  border-top: 1px solid var(--rule);
  padding-top: clamp(44px, 6vw, 64px);
  padding-bottom: clamp(44px, 6vw, 64px);
}

.home-section-title {
  font-family: var(--display);
  font-size: clamp(1.6rem, 3.6vw, 2.15rem);
  font-weight: 600;
  letter-spacing: -0.018em;
  margin: 0 0 8px;
  text-wrap: balance;
}

.home-section-lede {
  font-size: 1.03rem;
  line-height: 1.62;
  color: var(--muted);
  margin: 0 0 20px;
  max-width: 34em;
}

/* The ruled page, on the one section that is mostly prose. */
.home-steps {
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0 31px,
    var(--paper-rule) 31px 32px
  );
  background-position: 0 6px;
}

.home-step-grid {
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: clamp(24px, 4vw, 34px);
}

.home-step h3 {
  font-family: var(--display);
  font-size: 1.2rem;
  font-weight: 600;
  margin: 0 0 8px;
}

.home-step p {
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--muted);
  margin: 0;
}

.home-step-n {
  display: block;
  font-family: var(--hand);
  font-size: 1.7rem;
  line-height: 1;
  color: var(--margin-line);
  margin-bottom: 6px;
}

/* -------------------------------------------------------- mastery ladder --- */

.home-ladder {
  background: var(--paper-sunk);
}

.home-ladder-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
  gap: clamp(28px, 5vw, 56px);
  align-items: center;
}

.home-aside {
  font-family: var(--hand);
  font-size: 1.2rem;
  line-height: 1.35;
  color: var(--margin-line);
  margin: 18px 0 0;
}

.home-rungs {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
}

.home-rung {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  gap: 14px;
  align-items: center;
  padding: 15px 18px;
  border-bottom: 1px solid var(--rule);
  font-size: 0.96rem;
  font-weight: 600;
}
.home-rung:last-child {
  border-bottom: 0;
}

.home-rung-label {
  min-width: 0;
}

.home-rung-why {
  font-size: 0.82rem;
  font-weight: 400;
  color: var(--muted);
}

.home-tick {
  color: var(--accent);
  font-weight: 700;
}

/* Not yet earned — the point of the whole section, so it must read as pending. */
.home-rung-off {
  color: var(--muted);
  font-weight: 400;
}
.home-rung-off .home-tick {
  color: var(--rule-strong);
}

/* --------------------------------------------------------------- safety --- */

.home-trust {
  padding: clamp(44px, 6vw, 62px) clamp(20px, 5vw, 56px);
  background: var(--ink);
  color: var(--bg);
}
.home-trust .home-section-title,
.home-trust h3 {
  color: var(--bg);
}
.home-trust .home-section-lede {
  color: var(--rule-strong);
}

.home-trust-grid {
  list-style: none;
  margin: 30px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: clamp(24px, 4vw, 30px);
}

.home-trust-item h3 {
  font-size: 1.02rem;
  font-weight: 600;
  margin: 0 0 7px;
}

.home-trust-item p {
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--rule-strong);
  margin: 0;
}

.home-trust-n {
  display: block;
  font-family: var(--hand);
  font-size: 1.4rem;
  color: var(--accent);
  margin-bottom: 4px;
}

/* -------------------------------------------------------------- closing --- */

.home-end {
  padding: clamp(44px, 6vw, 64px) clamp(20px, 5vw, 56px);
  border-top: 1px solid var(--rule);
}

.home-end-inner {
  max-width: 34em;
}

.home-footer {
  padding: 24px clamp(20px, 5vw, 56px) 40px;
  border-top: 1px solid var(--rule);
}
.home-footer p {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.6;
  color: var(--muted);
}

/* ---------------------------------------------------------- narrow view --- */

@media (max-width: 860px) {
  /* One column, and the margin rule goes with it: a 1px red line beside a
     full-bleed column reads as a rendering artefact rather than a workbook. */
  .home-hero,
  .home-ladder-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .home-hero::before,
  .home-steps::before,
  .home-ladder::before {
    display: none;
  }

  .home-hero,
  .home-steps,
  .home-ladder {
    padding-left: clamp(20px, 5vw, 56px);
  }

  .home-demo {
    transform: none;
  }

  .home-nav {
    gap: 14px;
  }
  .home-nav-links {
    gap: 16px;
  }
}

/* ------------------------------------------------------- quick practice --- */

/* Orange, because it is "go here next" — the one card on her home page that is a suggestion of
   something to do now rather than a page she already has. */
.practice-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0 0 20px;
  padding: 16px 20px;
  background: var(--warm-soft);
  border: 2px solid var(--warm);
  border-radius: var(--r-md);
  box-shadow: 0 2px 0 var(--warm-dark);
  text-decoration: none;
  color: var(--ink);
  font-family: var(--sans);
}
.practice-card:hover,
.practice-card:focus-visible {
  background: var(--warm);
  color: var(--on-warm);
}
.practice-card-title {
  font-family: var(--serif);
  font-size: 1.25rem;
  font-weight: 700;
}
.practice-card-detail {
  font-weight: 600;
}
.practice-card-why {
  font-size: 0.9rem;
  opacity: 0.85;
}

.practice-main .practice {
  max-width: 720px;
}
/* `.lesson-btn` sets display, which beats the UA's [hidden] rule; the Next button is hidden until she
   has answered. */
.practice-main [hidden] {
  display: none !important;
}
.practice-main a.lesson-btn {
  text-decoration: none;
}
.practice-progress {
  margin: 8px 0 0;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.practice-progress:focus,
.practice-score:focus {
  outline: none;
}
.practice-from {
  margin: 2px 0 12px;
  font-size: 0.9rem;
  color: var(--muted);
}
.practice-score {
  font-family: var(--serif);
  font-size: 2rem;
  margin: 8px 0 4px;
}
.practice-line {
  font-size: 1.05rem;
}
.practice-missed {
  display: grid;
  gap: 10px;
  margin: 12px 0 20px;
  padding: 0;
  list-style: none;
}
.practice-missed li {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 12px 16px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}
.practice-missed-q {
  font-weight: 600;
}
.practice-missed-a {
  color: var(--good);
}
.practice-missed-a::before {
  content: "✓ ";
}

/* Shown after her first move in an order block, until she checks. */
.order-nudge {
  margin: 8px 0 0;
  font-size: 0.95rem;
  color: var(--accent-ink);
}

/* ------------------------------------------------------- parent view --- */

.chip-look {
  background: var(--try-soft);
  border-color: var(--try-line);
  color: var(--try);
}

/* "This week": the first thing a parent reads, so it gets room to breathe. */
.story {
  padding: 18px 22px;
  margin-bottom: 16px;
}
.story h2 {
  margin-top: 0;
}
.story p {
  font-size: 1rem;
  line-height: 1.6;
}
.story-pages {
  font-size: 0.92rem;
  color: var(--muted);
}
.story-chips {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 10px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.92rem;
}
.story-chip {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  font-weight: 600;
  text-decoration: none;
}
.story-chip--up {
  background: var(--good-soft);
  border-color: var(--good-line);
  color: var(--good);
}
.story-chip--look {
  background: var(--try-soft);
  border-color: var(--try-line);
  color: var(--try);
}

.topic-group {
  margin: 16px 0 6px;
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.topic-group:first-child {
  margin-top: 0;
}
.topic-list {
  margin: 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  overflow: hidden;
}
.topic-list li + li {
  border-top: 1px solid var(--rule);
}
.topic-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 10px 14px;
  color: var(--ink);
  text-decoration: none;
}
.topic-row:hover,
.topic-row:focus-visible {
  background: var(--accent-soft);
}
.topic-name {
  flex: 1 1 200px;
  font-weight: 600;
}
.topic-score {
  font-size: 0.88rem;
  color: var(--muted);
}

.behind-the-scenes {
  margin: 16px 0;
}
.behind-the-scenes > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 0.92rem;
  padding: 6px 0;
}
.behind-the-scenes > .card {
  margin-top: 10px;
}

/* The topic page's ladder: four steps, the current one filled, each reached step dated. */
.rung-track {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 6px;
  margin: 0 0 12px;
  padding: 0;
  list-style: none;
}
.rung {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 4px;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
  background: var(--paper-sunk);
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
}
.rung.is-reached {
  color: var(--ink);
}
.rung.is-current {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-ink);
  font-weight: 700;
}
.rung-label {
  display: block;
}
.rung-date {
  font-size: 0.75rem;
  font-weight: 400;
}

.answer-list {
  margin: 0;
  padding: 0;
  list-style: none;
}
.answer {
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
}
.answer:last-child {
  border-bottom: 0;
}
.answer-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
}
.answer-q {
  font-weight: 600;
}
.answer-result {
  white-space: nowrap;
  font-weight: 600;
}
.answer-result.is-right {
  color: var(--good);
}
.answer-result.is-not-yet {
  color: var(--try);
}
.answer-meta {
  margin: 4px 0 0;
  font-size: 0.88rem;
  color: var(--muted);
}
/* A repeat inside the counting window: shown for completeness, but it moved nothing. */
.answer.is-repeat {
  opacity: 0.6;
}

/* ------------------------------------------------------------ safety --- */

/* An alert must be impossible to miss, but read as calm: a parent may be about to read something
   frightening. Body text keeps the normal colour; only the frame and heading carry the warning. */
.safety-banner {
  color: var(--ink);
  font-weight: 400;
}
.safety-banner h2 {
  margin: 0 0 8px;
  font-size: 1.05rem;
  color: var(--try);
}
.safety-alerts {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
}
.safety-alert {
  padding: 10px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  background: var(--paper);
}
.safety-alert--urgent {
  border-left: 4px solid var(--try);
}
.safety-alert--seen {
  opacity: 0.7;
}
.safety-alert p {
  margin: 0 0 6px;
}
.safety-alert-head {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: baseline;
}
.safety-alert-words {
  margin: 6px 0 8px;
  padding: 6px 10px;
  border-left: 3px solid var(--rule-strong);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ─────────────── Read-aloud and talk-to-type (src/client/speech/) ───────────────

   Speaker buttons are made everywhere but shown only once an on-device voice is known to exist
   (reader.ts sets data-read-aloud on <html>). A device with no voice gets no buttons — never one
   that does nothing. */
.speak-btn {
  display: none;
}
:root[data-read-aloud] .speak-btn {
  -webkit-appearance: none;
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border: 2px solid var(--accent-line);
  border-radius: 50%;
  cursor: pointer;
  touch-action: manipulation;
}
:root[data-read-aloud] .speak-btn:hover {
  border-color: var(--accent);
}
:root[data-read-aloud] .speak-btn[aria-pressed="true"] {
  color: var(--on-accent);
  background: var(--accent);
  border-color: var(--accent);
}
.speak-btn:focus-visible,
.mic-btn:focus-visible {
  outline: 3px solid var(--warm);
  outline-offset: 2px;
}
.speak-icon,
.mic-icon {
  pointer-events: none;
}
/* In a lesson block: top right, with the block's text flowing around it. */
:root[data-read-aloud] .block > .speak-btn {
  float: right;
  margin: 0 0 8px 12px;
}
/* In a tutor message: beside "CLAUDE", smaller so the bubble stays a bubble. */
.msg .who {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
:root[data-read-aloud] .msg .who .speak-btn {
  min-width: 36px;
  min-height: 36px;
  font-size: 1rem;
}
.pill-row-speak {
  display: flex;
  align-items: center;
}
.pill-list > .pill-row-speak {
  list-style: none;
}

.mic-btn {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0;
  font-size: 1.25rem;
  color: var(--accent-ink);
  background: var(--accent-soft);
  border: 2px solid var(--accent-line);
  border-radius: var(--r-md);
  cursor: pointer;
  touch-action: manipulation;
}
.mic-btn[aria-pressed="true"] {
  color: var(--on-warm);
  background: var(--warm);
  border-color: var(--warm-dark);
}
.mic-btn:disabled {
  opacity: 0.6;
  cursor: progress;
}
.mic-status {
  margin: 0;
  padding: 0 14px 10px;
  font-family: var(--sans);
  font-size: 0.9rem;
  color: var(--muted);
  background: var(--paper);
}
.mic-status[hidden] {
  display: none;
}
