/* ============================================================
   Teacher Tash — Interactive Lesson Player
   Full-screen, step-through, cast-friendly. Tokens + fonts
   come from styles.css; this file adds the player chrome.
   ============================================================ */

:root { --bar-h: 64px; }

html, body { height: 100%; }
/* A column: bar, progress stops, then the stage taking whatever is left.
   The stage used to compute its own height as 100dvh minus two hardcoded
   constants, one of which (4px, the old thin progress bar) stopped being true
   when the bar became a row of tappable dots ~31px tall. The stage was that
   much taller than the screen, so the foot of EVERY slide sat below the fold.
   Letting flex do the arithmetic means it cannot drift out of date again. */
body {
  background: var(--cream); overflow: hidden; /* stage scrolls internally */
  display: flex; flex-direction: column; height: 100dvh;
}

/* ---------- Accent theming (per age band) ---------- */
html[data-accent="coral"] { --accent:#F26A4F; --accent-dark:#D8503A; --accent-tint:#FFE2D8; --accent-ink:#CC4227; }
html[data-accent="teal"]  { --accent:#14A696; --accent-dark:#0E8478; --accent-tint:#CFF0EA; --accent-ink:#0E8478; }
html[data-accent="lilac"] { --accent:#8E7BFF; --accent-dark:#6E5BF0; --accent-tint:#E4DEFF; --accent-ink:#5A47D6; }

/* ---------- Top control bar ---------- */
.lesson-bar {
  position: sticky; top: 0; z-index: 50;
  height: var(--bar-h); flex: 0 0 auto;   /* never squeezed by the stage */
  display: flex; align-items: center; gap: var(--s-4);
  padding: 0 var(--s-5);
  background: rgba(255, 248, 240, .9);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
.lb-back { display: inline-flex; align-items: center; gap: 4px; font-weight: 800; color: var(--ink); white-space: nowrap; }
.lb-back:hover { color: var(--accent-ink); text-decoration: none; }
.lb-back .arr { font-size: 1.3rem; line-height: 1; }
.lb-meta { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; flex: 1; }
/* One line, always. Left to wrap, a long stage line ("Stage 2 · Week 1 of 4 ·
   Grammar · Partitives") stacked four deep on a phone and spilled out of the
   bar across the progress dots. */
.lb-age { font-size: .7rem; font-weight: 800; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-title { font-family: var(--font-display); font-weight: 700; font-size: 1.04rem; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-controls { display: flex; align-items: center; gap: var(--s-2); }
.lb-btn {
  width: 42px; height: 42px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--paper);
  cursor: pointer; font-size: 1.25rem; color: var(--ink);
  display: grid; place-items: center;
  transition: background .2s ease, color .2s ease, transform .2s ease;
}
.lb-btn:hover:not(:disabled) { background: var(--accent-tint); color: var(--accent-ink); transform: translateY(-1px); }
.lb-btn:disabled { opacity: .35; cursor: default; }
.lb-btn.present { font-size: 1.05rem; }
.lb-counter { font-weight: 800; font-size: .88rem; color: var(--ink-soft); min-width: 52px; text-align: center; font-variant-numeric: tabular-nums; }

/* ---------- Progress bar ---------- */
.progress-track { position: sticky; top: var(--bar-h); z-index: 49; height: 4px; flex: 0 0 auto; background: var(--line-soft); }
.progress-bar { height: 100%; width: 0; background: linear-gradient(90deg, var(--accent), var(--accent-dark)); transition: width .35s var(--ease); }

/* ---------- Stage ---------- */
.stage {
  /* align (not place) — justify-items must stretch, or the slide is sized to
     its content and wide children overflow the screen horizontally. */
  display: grid; align-items: center; justify-items: stretch;
  /* Vertical padding tracks the VIEWPORT HEIGHT, not vmin. On a screenshared
     window the height is what runs out first, and 4vh of a tall desktop was
     being spent on air while the slide's bottom fell off the page. */
  padding: clamp(12px, 2.6vh, 48px) clamp(16px, 4vw, 64px);
  /* min-height:0 — without it a flex item refuses to shrink below its content,
     which would put the overflow back on the page instead of inside the stage. */
  flex: 1 1 auto; min-height: 0;
  overflow: auto;
}
.slide-inner {
  width: 100%; max-width: 70rem; margin-inline: auto;
  text-align: center;
  display: flex; flex-direction: column; align-items: center;
  /* Was a flat var(--s-5) = 24px. Fixed gaps never shrink, so a slide with six
     of them kept 144px of air no matter how short the screen got. */
  gap: clamp(10px, 2.2vh, 24px);
  /* Set by fitSlide() in js/lessons.js when a slide is still too tall to fit.
     transform-origin is the top edge so the slide shrinks downward into the
     space it has, rather than away from both edges at once. */
  transform: scale(var(--fit, 1));
  transform-origin: top center;
}
.stage.enter .slide-inner { animation: slideIn .45s var(--ease); }
/* The entry animation has to carry --fit through, or a scaled slide would snap
   back to full size for the length of the animation and overflow while it ran. */
@keyframes slideIn {
  from { opacity: 0; transform: translateY(18px) scale(calc(var(--fit, 1) * .985)); }
  to   { opacity: 1; transform: scale(var(--fit, 1)); }
}

.slide-kicker {
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 800; font-size: .8rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent-ink);
}
.slide-kicker::before { content: ""; width: 22px; height: 2px; background: var(--accent); border-radius: 2px; }
.slide-title { font-family: var(--font-display); font-weight: 700; line-height: 1.1; font-size: clamp(2rem, 7vmin, 4.4rem); margin: 0; color: var(--ink); }
.slide-title.med { font-size: clamp(1.6rem, 5vmin, 3rem); }
.slide-sub, .slide-cue { color: var(--ink-soft); font-size: clamp(1.05rem, 2.6vmin, 1.5rem); max-width: 46rem; margin: 0; }

/* ---------- Tip slides ----------
   Aimed at 7–9 year olds: big type, full-strength ink, and the key
   transformations pulled out as chips instead of buried in a paragraph. */
.tip-body { max-width: 52rem; margin: 0; display: flex; flex-direction: column; gap: clamp(14px, 2.4vmin, 26px); }
.tip-block { display: flex; flex-direction: column; gap: clamp(6px, 1.2vmin, 12px); align-items: center; }
.tip-line { color: var(--ink); font-size: clamp(1.15rem, 3vmin, 1.75rem); line-height: 1.45; margin: 0; font-weight: 600; }
.tip-line strong, .rule-text strong { color: var(--accent-ink); font-weight: 800; }

.chip-row { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: clamp(6px, 1.4vmin, 12px); }
.chip {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.2rem, 3.4vmin, 2.1rem); line-height: 1;
  color: var(--accent-ink); background: var(--paper);
  border: 3px solid var(--accent); border-radius: var(--r-pill);
  padding: clamp(7px, 1.4vmin, 13px) clamp(14px, 2.4vmin, 24px);
  box-shadow: var(--sh-sm);
}
/* Emphasis inside a chip marks what was ADDED — the -er/-est ending, the
   doubled letter, or the word "more" / "most". It gets its own colour and a
   soft highlight so the change stands out from the base word. */
.chip strong {
  color: var(--coral-dark); background: var(--sun-tint);
  border-radius: 5px; padding: 0 2px;   /* tight: these sit INSIDE a word */
}
.chip-arrow { font-size: clamp(1.1rem, 2.8vmin, 1.8rem); font-weight: 900; color: var(--accent); }

.rule-row { display: flex; align-items: flex-start; gap: clamp(10px, 1.8vmin, 16px); text-align: left; max-width: 44rem; }
.rule-num {
  flex: none; display: grid; place-items: center;
  width: clamp(30px, 4.4vmin, 42px); height: clamp(30px, 4.4vmin, 42px);
  border-radius: 50%; background: var(--accent); color: var(--paper);
  font-family: var(--font-display); font-weight: 700; font-size: clamp(1rem, 2.4vmin, 1.4rem);
}
.rule-text { color: var(--ink); font-size: clamp(1.1rem, 2.8vmin, 1.65rem); line-height: 1.45; font-weight: 600; }
.chip-stack { display: flex; flex-direction: column; gap: clamp(6px, 1.2vmin, 11px); align-items: center; }

/* "• phrase" — a worked example, given its own pill so it reads as a specimen. */
.example-pill {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.15rem, 3vmin, 1.85rem); color: var(--ink);
  background: var(--accent-tint); border-radius: var(--r-pill);
  padding: clamp(8px, 1.5vmin, 14px) clamp(16px, 2.6vmin, 26px);
}
.example-pill strong { color: var(--accent-ink); }

/* ❌ / ✅ pairs — wrong and right side by side. */
.judge-row {
  display: flex; align-items: center; gap: clamp(8px, 1.6vmin, 14px);
  font-size: clamp(1.05rem, 2.7vmin, 1.6rem); font-weight: 700; line-height: 1.35;
  padding: clamp(8px, 1.4vmin, 13px) clamp(14px, 2.2vmin, 20px);
  border-radius: var(--r-md); max-width: 44rem; text-align: left;
}
.judge-row.bad { background: rgba(242, 106, 79, .1); color: var(--ink); }
.judge-row.good { background: rgba(20, 166, 150, .12); color: var(--ink); }
.judge-mark { font-size: 1.15em; flex: none; }
.judge-row.bad .judge-text { text-decoration: line-through; text-decoration-color: rgba(242, 106, 79, .55); }

/* ---------- Sort activity (short vs long adjectives) ---------- */
/* A grid, not a wrapping row: sentence-length items would otherwise take a
   line each and push the buckets off the bottom of the screen. auto-fit gives
   three across on a laptop, two on a tablet, one on a phone. */
.sort-pool {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(8px, 1.5vmin, 13px);
  margin: 0 auto; max-width: 56rem; width: 100%;
  align-items: stretch;
}
@media (max-width: 900px) { .sort-pool { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 380px) { .sort-pool { grid-template-columns: 1fr; } }
.sort-tile {
  font-family: var(--font-display); font-weight: 700; cursor: pointer;
  font-size: clamp(1rem, 2.4vmin, 1.5rem); line-height: 1.25;
  color: var(--ink); background: var(--paper);
  border: 4px solid var(--line); border-radius: var(--r-lg);
  padding: clamp(10px, 1.7vmin, 16px) clamp(12px, 2vmin, 20px);
  display: grid; place-items: center; text-align: center; min-height: 3.2em;
  box-shadow: var(--sh-sm); transition: transform .18s var(--ease), border-color .18s var(--ease), background .18s var(--ease);
}
.sort-tile:hover { transform: translateY(-2px); }
.sort-tile.selected { border-color: var(--accent); background: var(--accent-tint); transform: translateY(-5px) scale(1.06); box-shadow: var(--sh); }
.sort-tile.wrong { animation: tileShake .45s var(--ease); border-color: var(--coral); }
.sort-tile.placed { cursor: default; border-color: var(--teal); background: rgba(20, 166, 150, .12); box-shadow: none; }
@keyframes tileShake { 0%,100% { transform: translateX(0); } 20% { transform: translateX(-8px); } 45% { transform: translateX(7px); } 70% { transform: translateX(-4px); } }

.sort-buckets { display: flex; flex-wrap: wrap; justify-content: center; gap: clamp(12px, 2.4vmin, 26px); width: 100%; max-width: 56rem; margin-inline: auto; }
.bucket {
  flex: 1 1 11rem; min-height: clamp(104px, 20vh, 230px);
  border: 5px dashed var(--line); border-radius: var(--r-lg);
  padding: clamp(10px, 1.8vmin, 18px); background: var(--cream-2);
  display: flex; flex-direction: column; gap: clamp(6px, 1.2vmin, 12px); align-items: center;
  transition: border-color .2s var(--ease), background .2s var(--ease);
}
.bucket.armed { border-color: var(--accent); background: var(--accent-tint); }
.bucket.hit { animation: bucketPop .45s var(--ease); }
@keyframes bucketPop { 0% { transform: scale(1); } 45% { transform: scale(1.05); } 100% { transform: scale(1); } }
.bucket-label { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.25rem, 3.2vmin, 2rem); color: var(--accent-ink); }
.sort-buckets .bucket:first-child { border-color: rgba(20, 166, 150, .5); background: rgba(20, 166, 150, .07); }
.sort-buckets .bucket:first-child .bucket-label { color: var(--teal-dark); }
.sort-buckets .bucket:last-child { border-color: rgba(242, 106, 79, .5); background: rgba(242, 106, 79, .07); }
.sort-buckets .bucket:last-child .bucket-label { color: var(--coral-dark); }
.bucket-items { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; }
.bucket-items .sort-tile { min-height: 0; font-size: clamp(.9rem, 2vmin, 1.2rem); padding: 8px 14px; border-radius: var(--r-pill); }
.sort-hint { color: var(--ink-soft); font-size: clamp(.95rem, 2.3vmin, 1.25rem); margin: 0; font-weight: 600; }
.sort-progress { font-weight: 800; color: var(--accent-ink); font-size: clamp(1rem, 2.5vmin, 1.4rem); }
.slide-prompt { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.5rem, 5vmin, 3rem); line-height: 1.2; color: var(--ink); margin: 0; max-width: 52rem; }
.big-emoji { font-size: clamp(3rem, 12vmin, 6rem); line-height: 1; }

/* ---------- Tap-to-hear card ---------- */
.hear-card {
  border: 2px solid var(--accent); border-radius: var(--r-xl);
  background: linear-gradient(160deg, var(--accent-tint), var(--paper));
  padding: clamp(28px, 6vmin, 56px) clamp(40px, 10vmin, 96px);
  cursor: pointer; box-shadow: var(--sh);
  display: flex; flex-direction: column; align-items: center; gap: var(--s-3);
  transition: transform .2s var(--ease), box-shadow .2s ease;
  max-width: 100%;
}
.hear-card:hover { transform: translateY(-3px); box-shadow: var(--sh-lg); }
.hear-card.speaking { animation: hearPulse 1s ease-in-out infinite; }
@keyframes hearPulse { 0%, 100% { box-shadow: var(--sh); } 50% { box-shadow: 0 0 0 14px var(--accent-tint), var(--sh); } }
.hear-big { font-family: var(--font-display); font-weight: 700; font-size: clamp(5rem, 22vmin, 15rem); line-height: 1; color: var(--accent-ink); }
.hear-hint { font-weight: 800; font-size: clamp(.95rem, 2.2vmin, 1.2rem); color: var(--ink-soft); }

/* ---------- Blend tiles ---------- */
.tiles { display: flex; gap: clamp(10px, 2vmin, 20px); justify-content: center; flex-wrap: wrap; }
.tile {
  min-width: clamp(80px, 14vmin, 150px);
  border: 3px solid var(--accent); border-radius: var(--r-lg);
  background: var(--paper); box-shadow: var(--sh-sm);
  cursor: pointer; padding: clamp(14px, 2.5vmin, 26px);
  display: grid; place-items: center;
  transition: transform .15s var(--ease), background .2s ease, box-shadow .2s ease;
}
.tile:hover { transform: translateY(-4px); box-shadow: var(--sh); }
.tile.speaking { background: var(--accent-tint); }
.tile-g { font-family: var(--font-display); font-weight: 700; font-size: clamp(3rem, 12vmin, 8rem); line-height: 1; color: var(--accent-ink); }
.blend-word {
  margin-top: var(--s-4); border: 0; border-radius: var(--r-pill);
  background: var(--accent-ink); color: #fff;
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.8rem, 6vmin, 3.5rem);
  padding: clamp(12px, 2vmin, 22px) clamp(24px, 5vmin, 44px);
  box-shadow: var(--sh-coral); cursor: pointer;
  display: inline-flex; align-items: center; gap: 12px;
  transition: transform .2s var(--ease);
}
.blend-word:hover { transform: translateY(-3px); }
.blend-word .hear-hint { color: rgba(255, 255, 255, .85); font-size: 1rem; }
.blend-word.speaking { animation: hearPulse 1s ease-in-out infinite; }

/* ---------- Choose ---------- */
.choices { display: flex; flex-wrap: wrap; gap: var(--s-3); justify-content: center; max-width: 60rem; }
.choice {
  border: 4px solid var(--accent); border-radius: var(--r-lg); background: var(--paper);
  padding: clamp(16px, 2.4vmin, 26px) clamp(24px, 3.4vmin, 38px);
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.5rem, 4.8vmin, 2.8rem); color: var(--accent-ink);
  cursor: pointer; box-shadow: var(--sh);
  transition: transform .15s var(--ease), background .2s ease, border-color .2s ease, color .2s ease;
}
.choice:hover, .choice:focus-visible { transform: translateY(-4px) scale(1.03); background: var(--accent-tint); }
.choice.wrong { animation: shake .45s ease; background: #FFF0EE; border-color: var(--coral-dark); }
.choice.correct { background: var(--teal-tint); border-color: var(--teal-dark); color: var(--teal-dark); }
@keyframes shake { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-9px); } 40% { transform: translateX(9px); } 60% { transform: translateX(-6px); } 80% { transform: translateX(6px); } }
.choose-feedback { font-weight: 800; font-size: clamp(1.1rem, 3vmin, 1.6rem); color: var(--teal-dark); margin: 0; }

/* ---------- Read-together ---------- */
.passage {
  margin: 0; max-width: 60rem;
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(1.3rem, 4.6vmin, 3rem); line-height: 1.55; color: var(--ink);
}
.sent { cursor: pointer; border-radius: 8px; padding: 2px 4px; transition: background .2s ease; }
.sent:hover { background: var(--accent-tint); }
.cues-wrap { display: flex; flex-direction: column; align-items: center; gap: var(--s-2); max-width: 50rem; }
.cues { display: flex; flex-direction: column; gap: 6px; }
.cues li { position: relative; padding-left: 26px; color: var(--ink-soft); font-weight: 600; font-size: clamp(.95rem, 2.4vmin, 1.25rem); }
.cues li::before { content: "💡"; position: absolute; left: 0; top: 0; }
.hear-all { margin-top: var(--s-2); }

/* ---------- Reveal ---------- */
.reveal-answer {
  font-size: clamp(1.15rem, 3.2vmin, 1.8rem); line-height: 1.6; color: var(--ink);
  background: var(--accent-tint); border-radius: var(--r-lg);
  padding: clamp(16px, 2vmin, 26px); max-width: 52rem; font-weight: 600; margin: 0;
}

/* ---------- Your turn ---------- */
.yt-inner .btn { font-size: clamp(1.05rem, 2.8vmin, 1.4rem); }
.reveal-inner .btn { font-size: clamp(1.15rem, 3.2vmin, 1.7rem); padding: clamp(14px, 2.2vmin, 22px) clamp(26px, 3.6vmin, 40px); }

/* ---------- Present / cast mode ---------- */
body.presenting { overflow: hidden; }
body.presenting .lb-back,
body.presenting .lb-meta { display: none; }
body.presenting .lesson-bar {
  position: fixed; top: auto; bottom: 18px; left: 50%;
  transform: translateX(-50%);
  height: auto; padding: 7px 10px; gap: 8px;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  background: rgba(255, 248, 240, .92); box-shadow: var(--sh);
}
body.presenting .progress-track { display: none; }
/* No height override needed in present mode: the bar goes position:fixed and
   the stops are hidden, so the stage is the only item left in the column and
   flex hands it the full height on its own. */

/* ---------- Responsive ---------- */
@media (max-width: 620px) {
  .lb-back .lbl { display: none; }       /* arrow only on tight screens */
  /* Seven controls leave the meta column ~56px wide — enough for one truncated
     line, not two. The stage line goes; the title stays. */
  .lb-age { display: none; }
  .lb-title { max-width: 38vw; }
  .slide-inner { gap: var(--s-4); }
  .lb-counter { min-width: 44px; font-size: .82rem; }
  /* Seven controls do not fit a phone at 42px each — the row overflowed the
     viewport sideways once the pen joined it. Shrink the buttons and close up
     the gaps rather than dropping a control the teacher needs mid-lesson.
     Scoped to .lesson-bar so these beat the base rules further down the file —
     at equal specificity the later rule wins, and .star-pill is defined below. */
  .lesson-bar { padding: 0 10px; gap: var(--s-2); }
  .lesson-bar .lb-controls { gap: 4px; }
  .lesson-bar .lb-btn { width: 34px; height: 34px; font-size: 1.05rem; }
  .lesson-bar .lb-btn.blank,
  .lesson-bar .lb-btn.draw,
  .lesson-bar .lb-btn.present { font-size: .92rem; }
  .lesson-bar .star-pill { font-size: .86rem; padding: 6px 10px; margin-right: 0; }
}
/* The last 4px, on the narrowest phones still in use. */
@media (max-width: 340px) {
  .lesson-bar .lb-counter { min-width: 30px; font-size: .76rem; }
}

/* ---------- Fun: mascot, stars, confetti, bounce ---------- */
.lesson-mascot {
  /* vh, not vmin: on a wide-but-short shared window vmin still tracks the
     height, but a full-width desktop spends it on a character that crowds out
     the words underneath. */
  width: clamp(72px, 14vh, 150px); height: auto; margin-bottom: var(--s-2);
  filter: drop-shadow(0 10px 18px rgba(58, 46, 42, .18)); user-select: none;
}
.lesson-mascot.wave { transform-origin: 50% 90%; animation: mascotWave 2.6s ease-in-out infinite; }
.lesson-mascot.celebrate { animation: mascotPop .9s var(--ease); }
@keyframes mascotWave { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }
@keyframes mascotPop {
  0% { transform: scale(.55) rotate(-8deg); opacity: 0; }
  60% { transform: scale(1.12) rotate(4deg); opacity: 1; }
  100% { transform: scale(1) rotate(0); }
}

/* Tash taps this to award a star mid-lesson, so it has to read as a control. */
.star-pill {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: inherit; font-weight: 800; font-size: 1rem;
  background: var(--sun-tint); color: #8a5a00;
  border: 2px solid rgba(255, 185, 46, .55);
  padding: 7px 15px; border-radius: var(--r-pill); margin-right: 4px;
  cursor: pointer; transition: transform .15s var(--ease), background .2s ease;
}
.star-pill:hover, .star-pill:focus-visible { background: #FFE9B8; transform: translateY(-1px) scale(1.05); }
.star-pill:active { transform: scale(.96); }
.star-pill.bump { animation: starBump .42s var(--ease); }
@keyframes starBump {
  0% { transform: scale(1); }
  40% { transform: scale(1.4) rotate(-8deg); background: var(--sun); }
  100% { transform: scale(1); }
}

.end-stars {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.2rem, 3.4vmin, 1.9rem); color: #B07200; margin: 0;
}

/* The wobble is FINITE — three cycles (~10s) of attention when the slide
   arrives, then rest. It used to run infinitely, which cost GPU for as long as
   any intro or onward slide sat open (a teaching slide sits for minutes) and
   made the CTA never look "stable" to scripted walks. Same settled remedy as
   the palIdle bob above: finite, retriggered by the next slide render. */
.btn.bouncy { animation: idleWobble 3.4s ease-in-out 3; }
@keyframes idleWobble { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(-1.3deg); } }
.bouncy-tile { animation: tilePop .42s var(--ease) backwards; }
@keyframes tilePop { 0% { transform: scale(.55); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }



/* ---------- Name personalisation ---------- */
.name-hi { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.3rem, 3.4vmin, 2rem); color: var(--accent-ink); margin: 0; }
.name-wrap { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-top: var(--s-2); }
.name-label { font-weight: 800; font-size: .9rem; color: var(--ink-soft); }
.name-input {
  font-family: var(--font-body); font-size: 1.2rem; font-weight: 700; text-align: center;
  color: var(--ink); padding: 12px 18px; width: min(320px, 80vw);
  border: 2px solid var(--accent); border-radius: var(--r-pill); background: var(--paper);
  box-shadow: var(--sh-sm);
}
.name-input:focus { outline: none; border-color: var(--accent-dark); box-shadow: 0 0 0 4px var(--accent-tint); }
.name-praise { color: var(--accent-ink); font-weight: 700; }

/* ---------- Adventure-path stops + sparkle ---------- */
.progress-stops { display: flex; align-items: center; justify-content: center; gap: clamp(4px, 1.1vw, 9px); height: auto; padding: 9px 12px; background: var(--paper); border-bottom: 1px solid var(--line); }
.progress-stops .stop { width: clamp(8px, 1.4vw, 11px); height: clamp(8px, 1.4vw, 11px); border-radius: 50%; border: 0; background: var(--line); cursor: pointer; padding: 0; transition: transform .2s var(--ease), background .2s ease; }
.progress-stops .stop:hover { transform: scale(1.35); background: var(--accent); }
.progress-stops .stop.done { background: var(--accent); }
/* The current-slide dot does NOT pulse. It used to, via an infinite box-shadow
   animation, and that single decorative animation cost ~150% of one core in the
   GPU process CONTINUOUSLY, on every slide, forever — measured in real Brave:
   154% with it on, 1.5% with just this one animation disabled, while the three
   transform-based character animations measured free. It is the reason the
   lesson glitched under a screen share.
   A compositable replacement was tried (a scaled ::after ring, transform +
   opacity) on the theory that transform beats box-shadow. Measured interleaved,
   it was WORSE: 232% vs 148%. So the pulse is simply gone. The dot is already
   bigger, darker and ringed, and .stop's existing transition animates that
   change once when it becomes current — which costs nothing. */
.progress-stops .stop.current { background: var(--accent-ink); width: clamp(12px, 2.2vw, 16px); height: clamp(12px, 2.2vw, 16px); box-shadow: 0 0 0 4px var(--accent-tint); }



/* ---------- Reactive corner characters ----------
   Tash on the right, the lesson's animal friend on the left. */
.mascot-box, .animal-box { position: fixed; bottom: clamp(12px, 2vw, 26px); z-index: 60; display: flex; flex-direction: column; align-items: center; gap: 2px; pointer-events: none; }
.mascot-box { right: clamp(12px, 2vw, 26px); }
.animal-box { left: clamp(12px, 2vw, 26px); }
.mascot-box img, .animal-box img { width: clamp(74px, 11vmin, 112px); height: auto; filter: drop-shadow(0 8px 14px rgba(58, 46, 42, .18)); transition: transform .3s var(--ease); }
/* Pip is a smaller creature than Tash — scale him to sit as a companion. */
/* The bob WAKES on each new slide, then rests. It used to run infinitely and
   cost ~120-210% of one core for as long as the slide was on screen — and a
   teaching slide sits for minutes. Measured in real Brave: infinite bob 121-210%,
   finite bob 0.0%. Nothing else moved the number — not removing the drop-shadow
   filter (116%), not will-change (177%, worse), not changing the box's
   positioning (242%, worse), not taking var() out of the keyframe (no change).
   Only stopping it. Three cycles is ~10s of life exactly when the child's
   attention arrives on the slide, then silence. The character still reacts to
   answers — those are one-shot animations and are untouched. */
.animal-box img { width: clamp(64px, 9.5vmin, 96px); }
.animal-box img.bob { animation: palIdle 3.4s ease-in-out 3; }
.mascot-box.m-happy img, .animal-box.m-happy img { animation: mascotBounce .55s var(--ease); }
.mascot-box.m-idea img, .animal-box.m-idea img { animation: mascotWiggle .5s var(--ease); }
@keyframes mascotBounce { 0% { transform: scale(1); } 40% { transform: scale(1.16) translateY(-9px); } 100% { transform: scale(1); } }
@keyframes mascotWiggle { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(-6deg); } 75% { transform: rotate(6deg); } }
/* A gentle breathing bob so the corner never feels like a static sticker.
   The bob carries NO var() and NO scale. Growth lives on the standalone `scale`
   property (see "Feeding Pip"), which composes with `transform` instead of
   fighting it — so the old "the bob must carry scale(var(--grow))" rule is
   retired, and with it the reason this animation ran on the main thread.
   A var() inside an animated transform cannot be composited: it forced a
   restyle every frame and measured ~125% of one core, on every slide with a
   character on screen. */
@keyframes palIdle {
  0%, 100% { transform: translateY(0) rotate(-1deg); }
  50%      { transform: translateY(-5px) rotate(1deg); }
}
.mascot-bubble { background: var(--paper); color: var(--ink); font-weight: 800; font-size: .82rem; padding: 6px 12px; border-radius: var(--r-pill); box-shadow: var(--sh-sm); opacity: 0; transform: translateY(6px) scale(.9); transition: opacity .2s ease, transform .2s ease; white-space: nowrap; position: relative; margin-bottom: 4px; }
.mascot-bubble.show { opacity: 1; transform: none; }
.mascot-bubble::after { content: ""; position: absolute; bottom: -5px; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: var(--paper); }
@media (max-width: 520px) {
  .mascot-box img { width: clamp(58px, 17vw, 80px); }
  .animal-box img { width: clamp(50px, 15vw, 70px); }
  .mascot-bubble { font-size: .72rem; padding: 4px 9px; }
}
body.presenting .mascot-box { bottom: 74px; right: 18px; }
body.presenting .animal-box { bottom: 74px; left: 18px; }
@media (prefers-reduced-motion: reduce) { .animal-box img { animation: none !important; } }

/* ---------- Sticker reward ---------- */
/* HEIGHT IS EXPLICIT, and must stay that way. `aspect-ratio: 1` alone did not
   hold: `.sticker-img` is width:100%/height:100% of a parent whose height was
   auto, so the image fell back to its own intrinsic ratio and the BOX grew to
   match the artwork. Measured 2026-08-17 at 1024x640 — the same award slide was
   78px tall with the near-square question-marks placeholder (394x400) and 102px
   with Basil (227x320), and those 24px pushed the "Lesson notes" button 3px past
   the end of the stage. Every animal now renders in the same square box and
   object-fit: contain letterboxes whatever shape the art is, so the next
   character cannot move the fold. */
.sticker-pop { position: relative; width: clamp(92px, 15vh, 156px); height: clamp(92px, 15vh, 156px); aspect-ratio: 1; margin: clamp(4px, 1vh, 8px) 0 clamp(8px, 2vh, 16px); animation: stickerPop .7s var(--ease) backwards; }
.sticker-img { width: 100%; height: 100%; object-fit: contain; border-radius: 50%; border: 6px solid var(--sun); box-shadow: var(--sh), 0 0 0 3px var(--accent-ink); background: var(--paper); padding: 4px; }
.sticker-emoji { position: absolute; right: -2px; bottom: -2px; width: 40%; height: 40%; display: grid; place-items: center; font-size: clamp(1.1rem, 3vmin, 1.7rem); background: var(--paper); border: 3px solid var(--accent-ink); border-radius: 50%; box-shadow: var(--sh-sm); }
@keyframes stickerPop { 0% { transform: scale(.3) rotate(-12deg); opacity: 0; } 60% { transform: scale(1.12) rotate(5deg); opacity: 1; } 100% { transform: scale(1) rotate(0); } }
/* Mid-series: the friend is shown but not yet earned, so the reward stays a goal. */
.sticker-pop.locked .sticker-img { filter: grayscale(1) brightness(.35) opacity(.6); border-color: var(--line); box-shadow: none; }
.sticker-pop.locked .sticker-emoji { border-color: var(--line); }
.sticker-line { font-size: clamp(1rem, 2.6vmin, 1.25rem); color: var(--ink); margin: 0; }
.sticker-note { font-size: .72rem; color: var(--ink-faint); margin: var(--s-1) 0 0; }

/* ---------- "Show the story" modal (comprehension reference) ---------- */
.story-btn { margin-top: var(--s-4); font-size: .95rem; }
.story-modal { position: fixed; inset: 0; z-index: 200; background: rgba(58, 46, 42, .5); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); display: none; place-items: center; padding: var(--s-5); }
.story-modal.open { display: grid; }
.story-card { background: var(--paper); border-radius: var(--r-lg); box-shadow: var(--sh-lg); max-width: 46rem; width: 100%; max-height: 82vh; overflow: auto; padding: clamp(20px, 3vw, 34px); }
.story-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); margin-bottom: var(--s-4); }
.story-head strong { font-family: var(--font-display); font-size: 1.2rem; }
.story-close { border: 0; background: var(--cream-2); width: 38px; height: 38px; border-radius: 50%; font-size: 1.1rem; cursor: pointer; color: var(--ink); }
.story-close:hover { background: var(--coral-tint); color: var(--coral-ink); }
.story-text { margin: 0 0 var(--s-5); font-family: var(--font-display); font-weight: 500; font-size: clamp(1.1rem, 2.4vmin, 1.4rem); line-height: 1.6; color: var(--ink); }
/* The lesson's target words, highlighted in the modal recap (storyWords on read slides) */
.story-text mark { background: var(--teal-tint); color: var(--teal-dark); font-weight: 800; border-radius: 6px; padding: 0 4px; }
.story-foot { text-align: center; }
body.presenting .story-modal { z-index: 300; }

/* ---------- Lesson animal (intro + read companion) ---------- */
.lesson-mascot.intro-animal { width: clamp(88px, 18vh, 190px); }
.animal-meet { font-family: var(--font-display); font-weight: 700; font-size: clamp(1.2rem, 3vmin, 1.7rem); color: var(--accent-ink); margin: 0; }
.read-animal { width: clamp(72px, 11vmin, 104px); height: auto; margin: 0 0 var(--s-3); }

@media (prefers-reduced-motion: reduce) {
  .stage.enter .slide-inner,
  .hear-card.speaking, .blend-word.speaking, .choice.wrong,
  .lesson-mascot.wave, .lesson-mascot.celebrate,
  .btn.bouncy, .bouncy-tile, .star-pill.bump, .confetti-piece,
  .stop.current, .sparkle-piece, .sticker-pop,
  .mascot-box.m-happy img, .mascot-box.m-idea img { animation: none !important; }
  .confetti-piece, .sparkle-piece { display: none !important; }
}

/* ---------- Feeding Pip ----------
   A snack flies from the right answer to Pip, who munches and grows. The meter
   spans the whole ESL series, so it keeps filling from one lesson to the next. */
/* Growth uses the standalone `scale` property, not `transform: scale()`. That
   is what lets the idle bob animate `transform` with no var() in it — the two
   properties compose, so neither has to restate the other. */
.animal-box img { scale: var(--grow, 1); transform-origin: bottom center; }
.animal-box img { transition: scale .5s var(--ease); }
.animal-box img.munch { animation: munch .6s var(--ease); }
/* One-shot on a correct answer, so its cost does not matter. It animates the
   `scale` property too, and therefore multiplies cleanly with --grow. */
@keyframes munch {
  0%   { scale: var(--grow, 1); }
  35%  { scale: calc(var(--grow, 1) * 1.22); }
  70%  { scale: calc(var(--grow, 1) * 1.06); }
  100% { scale: var(--grow, 1); }
}
.feed-meter { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
.feed-track { width: clamp(52px, 8vmin, 78px); height: 9px; border-radius: var(--r-pill); background: var(--line-soft); overflow: hidden; box-shadow: inset 0 1px 2px rgba(58,46,42,.12); }
.feed-fill { display: block; height: 100%; width: 0; border-radius: var(--r-pill); background: linear-gradient(90deg, var(--sun), var(--coral)); transition: width .45s var(--ease); }
.feed-num { font-weight: 800; font-size: .68rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.feed-meter.full .feed-fill { background: linear-gradient(90deg, var(--teal), var(--sun)); }
.feed-meter.full .feed-num { color: var(--teal-dark); }

/* The snack is the reward — big enough to read across a cast screen, and slow
   enough to follow with your eyes from the answer to the animal. */
.snack-fly {
  position: fixed; z-index: 9998; pointer-events: none;
  font-size: clamp(2.6rem, 6vmin, 4.2rem); line-height: 1;
  filter: drop-shadow(0 6px 12px rgba(58, 46, 42, .3));
  animation: snackFly .95s cubic-bezier(.35, .05, .35, 1) forwards;
}
@keyframes snackFly {
  0%   { transform: translate(-50%, -50%) scale(.4) rotate(-15deg); opacity: 0; }
  15%  { transform: translate(-50%, -50%) scale(1.45) rotate(6deg); opacity: 1; }
  55%  { transform: translate(calc(-50% + var(--dx) * .55), calc(-50% + var(--dy) * .45 - 40px)) scale(1.25) rotate(-8deg); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(.7) rotate(10deg); opacity: 0; }
}

/* ---------- Snack picker ---------- */
.snack-tray {
  /* Sits above the animal in the left gutter. The container ignores pointer
     events so a tile underneath stays tappable — only the fruit buttons
     themselves catch clicks. */
  position: fixed; z-index: 95; pointer-events: none;
  left: clamp(12px, 2vw, 26px); top: calc(var(--bar-h) + 18px);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  background: var(--paper); border: 3px solid var(--sun);
  border-radius: var(--r-lg); padding: 10px 14px;
  box-shadow: 0 12px 28px rgba(58, 46, 42, .2);
  opacity: 0; transform: translateY(-10px) scale(.94);
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
.snack-tray.show { opacity: 1; transform: none; }
.snack-tray-label { font-weight: 800; font-size: .78rem; color: var(--ink-soft); white-space: nowrap; }
.snack-row { display: flex; gap: 8px; }
.snack-pick {
  pointer-events: auto;
  font-size: clamp(1.7rem, 3.6vmin, 2.3rem); line-height: 1;
  background: var(--cream-2); border: 3px solid transparent; border-radius: var(--r-pill);
  padding: 6px 10px; cursor: pointer;
  transition: transform .16s var(--ease), border-color .16s var(--ease), background .16s var(--ease);
}
.snack-pick:hover, .snack-pick:focus-visible { transform: translateY(-4px) scale(1.14); border-color: var(--sun); background: var(--sun-tint); }
.snack-pick:active { transform: scale(.94); }
@media (max-width: 860px) { .snack-tray { top: calc(var(--bar-h) + 12px); } }
@media (prefers-reduced-motion: reduce) {
  .snack-fly { display: none !important; }
  .snack-tray { transition: none; }
  .animal-box img.munch { animation: none !important; }
}

/* ---------- Size comparison (elephant vs mouse) ----------
   The two subjects are drawn at their real relative sizes via --size, so the
   child sees "bigger than" rather than being told it. */
.compare-row {
  display: flex; align-items: flex-end; justify-content: center;
  gap: clamp(10px, 2.6vmin, 34px); flex-wrap: wrap; width: 100%;
}
.compare-side {
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  position: relative;
}
.compare-img {
  width: calc(clamp(110px, 25vmin, 300px) * var(--size, 1));
  height: auto; display: block;
  filter: drop-shadow(0 10px 18px rgba(58, 46, 42, .16));
  transition: transform .2s var(--ease);
}
.compare-side:hover .compare-img, .compare-side:focus-visible .compare-img { transform: translateY(-4px) scale(1.04); }
.compare-cap {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(.95rem, 2.3vmin, 1.35rem); color: var(--ink);
}
.compare-word {
  position: absolute; top: -6px; left: 50%; transform: translate(-50%, 8px) scale(.8);
  background: var(--accent); color: var(--paper);
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(.9rem, 2.2vmin, 1.3rem);
  padding: 5px 14px; border-radius: var(--r-pill);
  opacity: 0; pointer-events: none; box-shadow: var(--sh-sm); white-space: nowrap;
}
.compare-side.said .compare-word { animation: sayWord 1.5s var(--ease); }
@keyframes sayWord {
  0% { opacity: 0; transform: translate(-50%, 8px) scale(.8); }
  18% { opacity: 1; transform: translate(-50%, -6px) scale(1.08); }
  75% { opacity: 1; transform: translate(-50%, -6px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -14px) scale(.95); }
}
.compare-rel {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.05rem, 2.8vmin, 1.75rem); color: var(--ink-soft);
  padding-bottom: clamp(14px, 3vmin, 40px);
}
.compare-rel strong { color: var(--accent-ink); }
@media (prefers-reduced-motion: reduce) { .compare-side.said .compare-word { animation: none !important; } }

/* ---------- End-of-lesson star explosion ----------
   Fires from the centre of the end slide; the count scales with stars earned. */
.star-fly {
  position: fixed; z-index: 9998; pointer-events: none;
  width: var(--sz, 34px); height: auto;
  filter: drop-shadow(0 4px 8px rgba(58, 46, 42, .22));
  animation: starFly 1.6s cubic-bezier(.15, .7, .35, 1) forwards;
}
@keyframes starFly {
  /* Clear the centre quickly so the praise line is never hidden for long. */
  0%   { transform: translate(-50%, -50%) scale(.2) rotate(0); opacity: 0; }
  8%   { transform: translate(calc(-50% + var(--dx) * .12), calc(-50% + var(--dy) * .12)) scale(1.1) rotate(calc(var(--rot) * .1)); opacity: 1; }
  60%  { opacity: .95; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy) + 110px)) scale(.5) rotate(var(--rot)); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .star-fly { display: none !important; } }

/* ---------- Blank screen for writing ----------
   Everything on the slide goes, plus both corner characters, leaving a clean
   surface to annotate over during a screen share. The control bar stays so the
   toggle back is always reachable — it turns solid while active. */
body.blanked .stage > * { visibility: hidden; }
body.blanked .mascot-box,
body.blanked .animal-box { display: none !important; }
body.blanked .progress-track { opacity: .25; }
.lb-btn.blank { font-size: 1.05rem; }
.lb-btn.blank[aria-pressed="true"] {
  background: var(--accent); color: var(--paper); border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
/* A quiet reminder of how to get back, in case the toggle is missed mid-lesson. */
body.blanked .stage::after {
  content: "Press B or the ✏️ button to bring the text back";
  visibility: visible;
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  font-family: var(--font-body); font-weight: 700; font-size: .8rem;
  color: var(--ink-faint); background: rgba(255, 248, 240, .8);
  padding: 6px 14px; border-radius: var(--r-pill); pointer-events: none;
}

/* ---------- Ink: pen and highlighter over the slide ----------
   Marking up a slide the way you mark up an iPhone screenshot. The canvas
   covers the whole player and is always painted, so marks stay put when the
   palette is closed; it only swallows the pointer while drawing is on, and
   the control bar is lifted above it so the way out stays reachable. */
.ink-canvas {
  position: fixed; inset: 0; z-index: 310;
  /* A canvas is a replaced element: inset:0 alone leaves it at its intrinsic
     300×150 in the corner, and every stroke lands off it. */
  width: 100%; height: 100%;
  /* IDLE BY DEFAULT. A full-viewport `mix-blend-mode` layer forces the
     compositor to blend the entire page every frame and defeats layer
     isolation — measured 2026-07-27 mid-lesson with WindowServer at 63% and
     kernel_task thermally throttling at 28% while screen-sharing. The blend is
     only needed when there is actually ink to blend, so it is applied only
     then (see .has-ink / body.drawing below). Most slides carry no ink, so
     most of the lesson now costs the compositor nothing. */
  mix-blend-mode: normal;
  pointer-events: none; touch-action: none;
}
/* Ink behaves like ink on paper: the highlighter darkens the page it covers
   instead of veiling it, so the word underneath keeps its full strength.
   Canvas compositing alone cannot do this — it never sees the slide behind the
   canvas — so the blend has to happen at the element level. */
.ink-canvas.has-ink,
body.drawing .ink-canvas { mix-blend-mode: multiply; }
body.drawing .ink-canvas { pointer-events: auto; cursor: crosshair; }
body.drawing .lesson-bar { z-index: 400; }
.lb-btn.draw { font-size: 1.05rem; }
.lb-btn.draw[aria-pressed="true"] {
  background: var(--accent); color: var(--paper); border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}

.ink-palette {
  position: fixed; z-index: 400;
  top: calc(var(--bar-h) + 12px); right: clamp(10px, 2vw, 22px);
  width: min(272px, calc(100vw - 24px));
  display: flex; flex-direction: column; gap: 7px;
  padding: 10px;
  background: rgba(255, 255, 255, .97);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border: 2px solid var(--line); border-radius: var(--r-lg);
  box-shadow: 0 14px 34px rgba(58, 46, 42, .22);
}
.ink-palette[hidden] { display: none; }
/* Present mode drops the control bar to the bottom, freeing the top corner. */
body.presenting .ink-palette { top: 12px; }

.ink-row { display: flex; gap: 6px; }
.ink-tool {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 7px 4px; cursor: pointer; line-height: 1; font-size: 1.15rem;
  font-family: var(--font-body); color: var(--ink);
  background: var(--cream-2); border: 2px solid var(--line); border-radius: var(--r-sm);
  transition: background .16s var(--ease), border-color .16s var(--ease), transform .16s var(--ease);
}
.ink-tool:hover { transform: translateY(-1px); }
.ink-tool .ink-lbl { font-size: .6rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-soft); }
.ink-tool[aria-pressed="true"] { background: var(--accent-tint); border-color: var(--accent); }
.ink-tool[aria-pressed="true"] .ink-lbl { color: var(--accent-ink); }

/* Nine colours and the mix-your-own circle: two rows of five, which keeps every
   swatch a comfortable size instead of squeezing ten into one row. */
.ink-swatches { display: grid; grid-template-columns: repeat(5, 1fr); gap: 7px; justify-items: center; }
.ink-swatch {
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  background: var(--dot); border: 3px solid transparent;
  box-shadow: inset 0 0 0 1px rgba(58, 46, 42, .14);
  transition: transform .16s var(--ease), border-color .16s var(--ease);
}
.ink-swatch:hover { transform: scale(1.12); }
.ink-swatch[aria-pressed="true"],
.ink-swatch.is-active { border-color: var(--ink); transform: scale(1.08); }

/* Mix your own. Until a colour is chosen the circle is a colour wheel, so it
   reads as "any colour" rather than as a tenth preset. */
.ink-custom {
  position: relative; overflow: hidden; display: block;
  background: conic-gradient(#F03E3E, #F59F00, #FFE066, #51CF66, #22B8CF, #4C6EF5, #BE4BDB, #F03E3E);
}
.ink-custom.has-colour { background: var(--dot); }
/* The picker's own control fills the circle invisibly, so the whole swatch is
   the hit target and the browser still opens its native colour panel. */
.ink-colour-input {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; border: 0; padding: 0; margin: 0; cursor: pointer;
  -webkit-appearance: none; appearance: none; background: none;
}
.ink-custom:focus-within { border-color: var(--ink); }

/* ---------- Eraser size ----------
   The same five slots the colours use, but each circle stands for a width.
   The dots are drawn to a compressed scale: at true size the 110px eraser
   would not fit the swatch and the 10px one would vanish beside it. */
.ink-size-pick {
  width: 32px; height: 32px; border-radius: 50%; cursor: pointer;
  border: 3px solid transparent; background: none; padding: 0;
  display: grid; place-items: center;
  transition: transform .16s var(--ease), border-color .16s var(--ease);
}
.ink-size-pick .dot {
  display: block; width: var(--d); height: var(--d); border-radius: 50%;
  background: var(--ink-soft); transition: background .16s var(--ease);
}
.ink-size-pick:hover { transform: scale(1.12); }
.ink-size-pick[aria-pressed="true"] { border-color: var(--ink); }
.ink-size-pick[aria-pressed="true"] .dot { background: var(--ink); }

.ink-size { align-items: center; gap: 9px; padding: 0 2px; }
.ink-range {
  flex: 1; min-width: 0; height: 6px; padding: 0; margin: 0;
  -webkit-appearance: none; appearance: none;
  background: var(--line); border-radius: var(--r-pill); cursor: pointer;
}
.ink-range:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.ink-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 18px; height: 18px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--paper);
  box-shadow: 0 1px 4px rgba(58, 46, 42, .3); cursor: pointer;
}
.ink-range::-moz-range-thumb {
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--paper);
  box-shadow: 0 1px 4px rgba(58, 46, 42, .3); cursor: pointer;
}
.ink-range::-moz-range-track { height: 6px; background: var(--line); border-radius: var(--r-pill); }
.ink-size-num {
  font-weight: 800; font-size: .7rem; color: var(--ink-soft);
  min-width: 26px; text-align: right; font-variant-numeric: tabular-nums;
}
.ink-size[hidden] { display: none; }

.ink-act {
  flex: 1; padding: 8px 4px; cursor: pointer; white-space: nowrap;
  font-family: var(--font-body); font-weight: 800; font-size: .72rem;
  color: var(--ink-soft); background: var(--paper);
  border: 2px solid var(--line); border-radius: var(--r-sm);
  transition: background .16s var(--ease), color .16s var(--ease);
}
.ink-act:hover { background: var(--cream-2); color: var(--ink); }
.ink-act.ink-done { background: var(--accent); border-color: var(--accent); color: var(--paper); }
.ink-act.ink-done:hover { background: var(--accent-dark); color: var(--paper); }

/* On a phone the palette sits along the bottom, clear of the control bar. */
@media (max-width: 620px) {
  .ink-palette { top: auto; bottom: 12px; left: 12px; right: 12px; width: auto; }
  body.presenting .ink-palette { top: auto; bottom: 78px; }
}
@media (prefers-reduced-motion: reduce) {
  .ink-tool, .ink-swatch, .ink-act { transition: none; }
}

/* ---------- Part-unlocked sticker ----------
   The grey friend sits underneath; a full-colour copy is clipped to the
   fraction of the series completed, so Pip literally fills in with colour. */
.sticker-pop.locked .sticker-fill {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: contain;
  border-radius: 50%; border: 6px solid var(--sun);
  background: var(--paper); padding: 4px;
  box-shadow: var(--sh), 0 0 0 3px var(--accent-ink);
  clip-path: inset(calc(100% - var(--fill, 0%)) 0 0 0);
  animation: fillRise .9s var(--ease) .35s backwards;
}
@keyframes fillRise { from { clip-path: inset(100% 0 0 0); } }
@media (prefers-reduced-motion: reduce) { .sticker-pop.locked .sticker-fill { animation: none; } }

/* ---------- Story page ----------
   A real two-page spread: the passage flows into two columns with the spine
   sitting in the gutter between them, so no line ever runs across the fold.
   Word-list passages opt out by having no `story`, so they stay plain. */
.read-inner { max-width: 82rem; }
.book {
  position: relative;
  width: 100%;
  background:
    linear-gradient(to right, rgba(58,46,42,.06), rgba(58,46,42,0) 5%, rgba(58,46,42,0) 95%, rgba(58,46,42,.06)),
    var(--paper);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 18px 44px rgba(58, 46, 42, .15), 0 2px 0 var(--line-soft);
  /* Block padding on vh — the page margins of the "book" were the single
     biggest slice of a passage slide that overflowed even a full desktop. */
  padding: clamp(16px, 3vh, 46px) clamp(26px, 4.4vmin, 60px);
  text-align: center;
}
/* the fold, centred in the column gutter — attached to the passage, not the
   book, or it runs straight through the title and part line above the columns */
.book .passage::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: 50%;
  width: 3px; transform: translateX(-50%);
  background: linear-gradient(to bottom,
    transparent, rgba(58,46,42,.05) 8%, rgba(58,46,42,.16) 50%, rgba(58,46,42,.05) 92%, transparent);
  border-radius: 3px; pointer-events: none;
}
.book-title {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.6rem, 4.6vmin, 2.9rem); line-height: 1.15;
  color: var(--ink); margin: 0 0 2px;
}
.book-part {
  display: block; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  font-size: .74rem; color: var(--accent-ink); margin-bottom: clamp(6px, 1.4vh, 22px);
}
.book .passage {
  position: relative;
  column-count: 2;
  column-gap: clamp(38px, 6.5vmin, 84px);
  column-fill: balance;
  text-align: left;
  hyphens: auto;
  font-size: clamp(1.05rem, 2.5vmin, 1.55rem);
  line-height: 1.62;
  max-width: none;
  margin-top: clamp(8px, 1.6vh, 22px);
}
/* a sentence should never be split across the fold */
.book .sent { break-inside: avoid; }
@media (max-width: 820px) {
  .book .passage { column-count: 1; text-align: center; }
  .book .passage::before { display: none; }
}

/* ---------- Lesson notes (teacher) ---------- */
.notes-wrap { width: 100%; max-width: 44rem; margin-inline: auto; }
.notes-toggle { font-size: .95rem; }
.notes-panel {
  margin-top: var(--s-3); text-align: left;
  background: var(--paper); border: 2px solid var(--line); border-radius: var(--r-lg);
  padding: clamp(14px, 2vmin, 22px); box-shadow: var(--sh-sm);
}
.notes-head { font-weight: 800; color: var(--ink); margin: 0 0 6px; font-size: .95rem; }
.notes-facts { list-style: none; padding: 0; margin: 0 0 10px; display: flex; flex-wrap: wrap; gap: 6px 16px; }
.notes-facts li { font-size: .85rem; color: var(--ink-soft); font-weight: 700; }
.notes-sub { font-weight: 800; font-size: .85rem; color: var(--ink); margin: 0 0 4px; }
.notes-stumbles { margin: 0 0 10px; padding-left: 18px; }
.notes-stumbles li { font-size: .84rem; color: var(--ink-soft); line-height: 1.45; margin-bottom: 3px; }
.notes-input {
  width: 100%; font-family: var(--font-body); font-size: .95rem; line-height: 1.5;
  padding: 10px 12px; border: 2px solid var(--line); border-radius: var(--r-md);
  background: var(--cream-2); color: var(--ink); resize: vertical;
}
.notes-input:focus { outline: none; border-color: var(--accent); }
.notes-actions { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.notes-status { font-weight: 800; font-size: .85rem; color: var(--teal-dark); }


/* ---------- Signed-in child ----------
   Added 2026-08-25 with pupil accounts. Nothing here fires for Tash teaching
   from her own machine; every rule needs either .star-pill-readonly or the
   sync dot, and neither exists unless a child is signed in. */

/* The star total a child can SEE but not add to. Tash's version is a button
   she taps to reward; this is the same pill with the button taken out of it,
   because a star count that disappears on sign-in reads as losing your stars. */
.star-pill-readonly {
  cursor: default;
  opacity: 1;               /* NOT the browser's disabled grey — it is not broken */
  pointer-events: none;
}

/* Has this child's work actually left the iPad? Three states, no words: green
   settled, amber in flight, grey saved-here-only. Tiny on purpose — a child
   who just got an answer right does not need a status message about networks,
   and Tash can read it across a room. */
.tt-sync {
  width: 9px; height: 9px; border-radius: 50%;
  display: none; flex: 0 0 auto; margin-inline-end: 2px;
  background: #9CCC8F;
  transition: background .25s ease, opacity .25s ease;
}
.tt-sync.saved,
.tt-sync.saving,
.tt-sync.offline { display: inline-block; }
.tt-sync.saving  { background: var(--sun, #FFC24A); }
.tt-sync.offline { background: #C9BDB6; }
@media (prefers-reduced-motion: no-preference) {
  .tt-sync.saving { animation: tt-sync-pulse 1.1s ease-in-out infinite; }
}
@keyframes tt-sync-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* The greeting that replaces the name box for a signed-in child. Same weight
   as the question it replaces, so the intro slide keeps its shape. */
.name-hello {
  font-family: Fraunces, Georgia, serif;
  font-size: clamp(1.2rem, 3.4vw, 1.6rem);
  font-weight: 800;
  margin: var(--s-4, 16px) 0 var(--s-5, 20px);
}


/* The end slide's "yes, it saved" line. Quiet and green when everything is
   away; amber and explicit when the work is still sitting on the tablet — that
   second state is the only one where closing the tab early would matter, so it
   is the one that must not be soothing. */
.saved-note {
  margin: var(--s-4, 16px) auto 0;
  font-weight: 800;
  font-size: clamp(.9rem, 2.2vw, 1rem);
  color: #4B7A3E;
  text-align: center;
}
.saved-note.waiting { color: #8A5A00; }
