/* ============================================================================
   story_read.css — THE STORY READING VIEW (layer 3 of 3: PAGE)
   ----------------------------------------------------------------------------
   Page-only layout + look for templates/story_detail.html (the reading screen:
   published-book scene layout, narration player, read-along highlight, the
   teaching layer — vocab/concept flashcards, spelling game). Built ONLY on
   var(--token) from tokens.css; never a raw brand hex. So a theme switch
   (data-theme) and a lighting switch (data-mode) recolour this whole screen
   automatically — there are deliberately NO per-mode override blocks here.

   The only literal colours kept are the semantic quiz/spelling green & red
   (right/wrong) and the science/math subject pills — category signals, not
   brand surface, so they stay constant across themes.

   ACCESSIBILITY — reader controls (2026-07-05): text size / dyslexia font /
   line spacing are set as CSS custom properties on <html> (--reader-size-scale,
   --reader-line-scale, [data-reader-font]) so the anti-FOUC script in this
   page's {% block head %} can apply a saved preference before first paint,
   the same mechanism base.html uses for the theme/mode switch. Every rule
   that reads them is scoped to this page's own reading classes, so nothing
   else in the design system is affected. See /styleguide for the token note.
   ============================================================================ */

/* Braille Institute's Atkinson Hyperlegible — recommended over OpenDyslexic
   (2026-07-05): both fonts share the same permissive SIL OFL 1.1 licence, but
   published research (Kuster et al. 2017/2018; Wery & Diliberto) found
   OpenDyslexic gives no reading-speed/accuracy benefit for dyslexic readers
   and wasn't preferred in blind tests, while Atkinson Hyperlegible was
   purpose-built and tested for maximising letter-shape distinction. It also
   reads as a clean sans next to the storybook serif rather than a novelty
   font. Loaded only here (page layer) since only this screen's reading
   surface can turn it on. */
@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap');
/* Arabic book serif for right-to-left stories (Arabic is the only full-story RTL
   language). Google Fonts serves only the Arabic subset (~well under 200KB) and
   the browser downloads it ONLY when an Arabic story actually renders — a normal
   LTR page never pays for it. Pairs with Source Serif 4's printed-book feel. */
@import url('https://fonts.googleapis.com/css2?family=Amiri&display=swap');

/* --- Page shell: a centred reading column (matches the old col-xl-8) and a
   roomier card for the book feel. The anon CTA + related rail share the wrap
   so they line up under the story. --- */
.read-wrap { max-width: 820px; margin: 0 auto; }
.read-body { padding: 32px 36px; }
@media (max-width: 600px) { .read-body { padding: 22px 18px; } }

/* --- Scene image slot (JS loader target: spinner / retry / lazy image) ----- */
.scene-image-slot {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  max-width: 600px;
  margin: 0 auto;
  border-radius: 12px;
  background: var(--surface-2);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  overflow: hidden;
}
.scene-image-slot.has-image { background: transparent; }
.scene-image-slot img { width: 100%; height: 100%; object-fit: cover; border-radius: 12px; }
.scene-image-slot .scene-spinner {
  width: 48px; height: 48px;
  border: 5px solid var(--rule);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: scene-spin 1s linear infinite;
  margin-bottom: 12px;
}
.scene-image-slot .scene-status { color: var(--fg2); font-size: 0.9rem; text-align: center; padding: 0 12px; }
.scene-image-slot .scene-retry { display: none; margin-top: 10px; padding: 6px 14px; font-size: 0.85rem; background: var(--accent); color: #fff; border: none; border-radius: 6px; cursor: pointer; }
.scene-image-slot.timed-out .scene-spinner { display: none; }
.scene-image-slot.timed-out .scene-retry { display: inline-block; }
.scene-image-slot.failed .scene-spinner { display: none; }
.scene-image-slot.failed .scene-retry { display: inline-block; }
@keyframes scene-spin { to { transform: rotate(360deg); } }

/* --- Published-book reading layout (decision 39 / feature #10, locked
   2026-06-13; ported from scripts/build_book_layout_preview.py). Each
   scene is its own flow-root block: the image FLOATS (alternating
   left/right on wide screens) and that scene's prose flows beside it, so
   text sits next to the picture like a printed children's book — and the
   float is contained, so text never leaks into the gap between pictures.
   On phones the floats turn off and it stacks (image, prose below). --- */
.story-title {
  color: var(--magic);                 /* brand 'magic' gold */
  font-weight: 600;
  font-size: clamp(1.8rem, 3.4vw, 2.6rem);
  margin: 0 auto 2.4rem;               /* generous buffer from heading to story */
  /* Symmetric gutters reserve a clear lane for the absolutely-positioned "Aa"
     control on the right (44px chip at right:2px). Without this a long title
     that wraps to two rows runs UNDER the Aa button on laptop-width screens
     (reported). Applied at the base so it holds at EVERY size ≥600px; the
     phone rule below switches to a left-aligned single right-gutter variant. */
  padding: 0 52px;
}

/* Anonymous "claim this story" banner — shown only to the session that created
   an unsaved story. Was an inline-flex pill (30px stadium radius) whose text and
   "Save it free" button both wrapped awkwardly on phones; now a proper responsive
   card: one row on wide screens, and on phones the text centers with a full-width
   button below (button never wraps mid-word). */
.anon-claim {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px 14px;
  justify-content: center; max-width: 600px; margin: 0 auto 1.4rem;
  padding: 12px 16px; border-radius: 16px;
  background: var(--magic-soft); border: 1px solid var(--magic); color: var(--magic-deep);
}
.anon-claim__icon { color: var(--magic); flex-shrink: 0; }
.anon-claim__text { flex: 1 1 auto; min-width: 150px; font-size: 0.9rem; font-weight: 600; line-height: 1.35; }
.anon-claim__btn { white-space: nowrap; flex-shrink: 0; }
@media (max-width: 520px) {
  .anon-claim { gap: 10px; text-align: center; }
  .anon-claim__text { flex-basis: 100%; text-align: center; }
  .anon-claim__btn { width: 100%; justify-content: center; }
}

/* DEPTH — the story's own read count, sitting quietly under the title. This is
   the ONE place a "how many times" number appears: a WORD only ever shows how
   many DIFFERENT stories it was met in, never how often they were read. Plain
   styled text (no icon font — decision 116's lesson). Sits in normal flow just
   below the title; the title's big 2.4rem buffer is tightened (below) only when
   this line follows, so the reads line stays tucked close without pushing the
   story down. (It used to lift itself up with a -1.9rem margin, but that
   negative overran the title's bottom margin on a TWO-ROW title and printed the
   "Read N times" line ON TOP of the wrapped title — reported on the long science
   theme at laptop width. Flow + a scoped title margin can't overlap at any size.) */
.story-reads {
  margin: 0.2rem auto 1.6rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--fg3);
}
/* Tighten the title→story buffer only when a reads line follows it, so that
   line sits close under the title instead of floating in the full 2.4rem gap.
   :has is supported by every browser this reading page targets; where it isn't,
   the title simply keeps its full buffer (a larger gap, never an overlap). */
.story-title-row:has(+ .story-reads) .story-title {
  margin-bottom: 0.6rem;
}

/* Generous leading: enough that a line whose vocab pill carries a dropped
   practice-language tag (absolutely positioned into the leading below the word)
   never crowds the next line, and spacing stays even whether or not a line has
   a pill. Tunable together with .vocab-pill .pill-word-lang { top }. */
.story-flow { line-height: calc(1.85 * var(--reader-line-scale, 1)); }
/* One block per picture: the picture floats and its EQUAL share of words
   (split by word count) sits beside it. Each block is its own row, so every
   picture always has its words. */
.scene-block { margin: 0 0 1.5rem; }
.scene-block:last-child { margin-bottom: 0; }
.scene-figure { --pw: 600px; --ph: 600px; margin: 0 0 1rem; }
.scene-figure .scene-image-slot { width: 100%; max-width: none; margin: 0; }
/* Size the <p> DIRECTLY: a framework rule sets font-size on p, which
   overrides a size merely inherited from a container (that broke the toddler
   size + the admin font slider before). pre-line preserves the LLM's own
   line breaks within a chunk (toddler rhyme couplets). */
.scene-prose p {
  margin: 0 0 1.05em;
  white-space: pre-line;
  font-size: calc(1rem * var(--story-font-scale, 1) * var(--reader-size-scale, 1));
}
.scene-prose p:last-child { margin-bottom: 0; }
html[data-reader-font="dyslexia"] .scene-prose p {
  font-family: 'Atkinson Hyperlegible', var(--font-ui, 'Nunito'), system-ui, sans-serif;
}

/* Mobile-first: floats off -> image (centered) then text, stacked. */
@media (max-width: 767px) {
  .scene-figure { max-width: min(var(--pw), 100%); margin: 0 auto 1.1rem; }
}
/* Wide screens: each block is a flow-root (contains its float); the picture
   floats (~half the column, alternating sides) and its words sit beside. */
@media (min-width: 768px) {
  .scene-block { display: flow-root; }
  .scene-figure { width: min(var(--pw), 50%); }
  .scene-figure.left  { float: left;  margin: 0.3rem 1.6rem 0.8rem 0; }
  .scene-figure.right { float: right; margin: 0.3rem 0 0.8rem 1.6rem; }
}

/* --- User story-edit button (floating quill beneath the title) ---- */
.story-edit-fab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 18px;
  border: none;
  border-radius: 30px;
  background: var(--magic-soft);
  color: var(--magic-deep);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background 0.2s, box-shadow 0.2s, color 0.2s, filter 0.2s;
}
.story-edit-fab:hover {
  background: var(--magic-soft);
  filter: brightness(0.96);
  box-shadow: var(--shadow-md);
}
.story-edit-fab i { font-size: 0.78rem; }

/* --- User edit modal: warm, inviting feel distinct from the admin chrome --- */
#editTextModal.user-modal .modal-content {
  border: none;
  border-radius: 20px;
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}
#editTextModal.user-modal .modal-header {
  background: linear-gradient(135deg, var(--magic-soft) 0%, var(--magic) 100%);
  border: none;
  padding: 22px 26px 18px;
}
#editTextModal.user-modal .modal-header .modal-title { color: var(--magic-deep); font-weight: 700; }
#editTextModal.user-modal .modal-header .modal-title i { color: var(--magic-deep); opacity: 0.75; }
#editTextModal.user-modal .modal-tagline {
  font-size: 0.83rem;
  color: var(--fg2);
  text-align: center;
  margin-bottom: 18px;
  font-style: italic;
}
#editTextModal.user-modal .edit-field-label {
  font-size: 0.77rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg2);
  margin-bottom: 5px;
}
#editTextModal.user-modal .form-control {
  border: 2px solid var(--rule);
  border-radius: 12px;
  background: var(--surface-2);
  color: var(--fg1);
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
#editTextModal.user-modal .form-control:focus {
  border-color: var(--magic);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--magic-soft);
  outline: none;
}
#editTextModal.user-modal .modal-footer {
  border: none;
  background: var(--surface-2);
  padding: 14px 26px 20px;
}
#editTextModal.user-modal .btn-save-story {
  background: linear-gradient(135deg, var(--magic) 0%, var(--magic-deep) 100%);
  border: none;
  border-radius: 12px;
  color: #fff;
  font-weight: 700;
  font-size: 0.92rem;
  padding: 9px 28px;
  box-shadow: var(--shadow-sm);
  transition: opacity 0.15s, box-shadow 0.15s;
}
#editTextModal.user-modal .btn-save-story:hover {
  opacity: 0.91;
  box-shadow: var(--shadow-md);
}

/* TODDLER format (decision 39 / feature #11, LOCKED 2026-06-13): few rhyming
   words on a big-type page so the page fills warmly. Rounded Nunito 600 at a
   larger admin-tunable size (toddler_font_pct), extra line spacing, larger gold
   title. Gated on story_format == "toddler". Hook renamed .read-body.toddler
   (was .card-body.toddler) so it never collides with Bootstrap's .card-body. */
.read-body.toddler .scene-prose p {
  font-family: 'Nunito', var(--font-ui, 'Open Sans'), system-ui, sans-serif;
  font-size: calc(1rem * var(--toddler-font-pct, 150) / 100 * var(--reader-size-scale, 1));
  line-height: calc(1.5 * var(--reader-line-scale, 1));
  font-weight: 600;
}
html[data-reader-font="dyslexia"] .read-body.toddler .scene-prose p {
  font-family: 'Atkinson Hyperlegible', var(--font-ui, 'Nunito'), system-ui, sans-serif;
}
.read-body.toddler .story-title {
  font-size: clamp(2.1rem, 3.6vw, 2.9rem);
}

/* --- Right-to-left stories (decision 2026-08-22; Arabic is the only full-story
   RTL language). `dir="rtl"` on .story-flow (set server-side from the story's
   language) makes the prose flow and align right; the Arabic book serif is used
   for the story text + title. Placed AFTER the toddler/dyslexia font rules so an
   Arabic toddler story still gets the Arabic face (equal specificity, later wins;
   Nunito/Atkinson carry no Arabic glyphs anyway). The reader chrome — controls,
   teaching cards — stays LTR by design; only the story text mirrors. --- */
.read-body.is-rtl .story-title,
.read-body.is-rtl .scene-prose p {
  font-family: 'Amiri', var(--font-prose, 'Source Serif 4'), Georgia, serif;
}
.read-body.is-rtl .scene-prose p {
  /* Arabic sits taller with its diacritics — a touch more leading reads calmer. */
  line-height: calc(1.9 * var(--reader-line-scale, 1));
}
/* Mirror the alternating scene-image floats so the picture sits on the correct
   side of right-to-left prose (only where floats are on, ≥768px). */
@media (min-width: 768px) {
  .story-flow[dir="rtl"] .scene-figure.left  { float: right; margin: 0.3rem 0 0.8rem 1.6rem; }
  .story-flow[dir="rtl"] .scene-figure.right { float: left;  margin: 0.3rem 1.6rem 0.8rem 0; }
}

/* --- Native-name reader language switcher (config language_label_style=native).
   Each pill stacks the endonym over a tiny English caption; on phones the caption
   is dropped since the pills are too cramped for two lines (native name only). --- */
.reader-langs--native .ds-pill,
.reader-langs--native .ds-btn { flex-direction: column; line-height: 1.0; padding-top: 5px; padding-bottom: 5px; }
.reader-langs--native .rl-native { display: block; }
.reader-langs--native .rl-en { display: block; font-size: 0.58rem; font-weight: 700; opacity: 0.6; line-height: 1; margin-top: 0; }
/* No-caption languages (endonym == English name, e.g. English): an INVISIBLE
   caption-slot placeholder holds the same height so the native name stays
   vertically aligned with the two-line pills beside it. */
.reader-langs--native .rl-en--ph { visibility: hidden; }
/* Active pill matches the sibling pills' rounded-RECTANGLE (--radius-input),
   not the stadium/oval chip radius — cleaner, especially when two lines tall. */
.reader-langs .ds-pill { border-radius: var(--radius-input); }
@media (max-width: 600px) {
  .reader-langs--native .rl-en { display: none; }
}

/* --- Teaching layer (Wave 2, decision 32) — visual baseline is
   mockup_teaching_layer.html. Overlay-only and additive: every rule below
   targets elements that exist only when the server passed a teaching
   context, so a plain story renders byte-identical to before. --- */
.targets-tray {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  align-items: center;
  margin: 0 0 20px;
  padding: 12px 16px;
  border-radius: 16px;
  background: var(--teach-soft); /* sage wash — teaching layer's reserved hue */
}
.targets-label {
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg2);
  margin-right: 4px;
}
/* For the words tray (not the idea banner) the label is a tiny helper row that
   sits on its own line at the top, so the word chips + language selector share
   one clean row below it instead of the label eating a whole row's width. It's
   redundant helper text, so keep it quiet and tight — small row-gap and a
   trimmed top so it doesn't claim vertical space. */
.targets-tray:not(.idea-tray) { gap: 4px 12px; padding-top: 9px; }
.targets-tray:not(.idea-tray) .targets-label {
  flex: 0 0 100%;
  margin: 0;
  font-size: 0.6rem;
  letter-spacing: 0.05em;
  opacity: 0.7;
  white-space: nowrap;
}
.target-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border-radius: 30px;
  font-size: 0.92rem;
  background: var(--surface);
  border: 1px solid var(--magic);
  box-shadow: var(--shadow-sm);
}
.target-chip i { color: var(--magic-deep); font-size: 0.85rem; }
/* Tap-to-hear replaces the old progress dot: a small, quiet speaker that plays
   the stored pronunciation only (hidden by JS when a language has none). */
.target-chip .chip-say {
  width: 20px; height: 20px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--magic-deep);
  cursor: pointer;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
}
.target-chip .chip-say:hover { background: var(--magic-soft); }
.target-chip .chip-say svg { width: 14px; height: 14px; }
/* An explicit display: overrides the [hidden] UA rule, so restore it — the
   speaker is hidden when the current language has no stored pronunciation. */
.target-chip .chip-say[hidden] { display: none; }

/* In-story vocabulary pill (decision 172) — the "marker" treatment. The word
   sits ON the normal text line with a translucent highlighter swiped across its
   x-height band only, so ascenders (d, l, h, t...) poke out above the ink like a
   real marker. When a practice language is chosen, the switcher injects a small
   sage TAG below the word — absolutely positioned into the line's leading, so it
   never lifts the word off the baseline. With no practice language it's simply
   the marked word. The story prose line-height (.story-flow) is set generous
   enough that the dropped tag never crowds the next line. */
/* The pill itself is the tight, positioned box hugging the word (line-height:1,
   inline-block, on the text baseline). The practice tag and heritage line are
   its direct children, so their top:100% anchors to the WORD's box — just below
   it — regardless of the prose font-size or line-height. */
.vocab-pill {
  position: relative;
  display: inline-block;
  line-height: 1;
  vertical-align: baseline;
  cursor: pointer;
}
.vocab-pill .pill-main { white-space: nowrap; cursor: pointer; }
/* The story-language word: a visible yellow marker swiped across the x-height
   band (ascenders/descenders poke out), reading like a highlighter over the
   dark story text. Percentages are relative to pill-main's tight line box. */
.vocab-pill .pill-word {
  font-weight: 600;
  color: var(--fg1);
  background: linear-gradient(to top, transparent 14%, var(--mark) 14%, var(--mark) 66%, transparent 66%);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  padding: 0 0.06em;
  border-radius: 2px;
  transition: background 0.16s ease;
}
.vocab-pill:hover .pill-word { --mark: var(--mark-strong); }
/* Heritage annotation (owner-only): the child's word-learning-language word,
   small, dropped under the printed word — same absolute placement as the
   practice tag so it never fragments the inline pill. (The two rarely co-occur;
   if an owner also picks a practice language they share this slot.) */
.vocab-pill .pill-heritage {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  margin-top: 2px;
  text-align: center;
  white-space: nowrap;
  padding-top: 4px;
  border-top: 1px dashed color-mix(in srgb, var(--teach-ink) 55%, transparent);
  font-size: 0.55em;
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--teach-ink);
  opacity: 0.8;
}
/* Practice-language TAG (decision 172, in-story): the chosen language's word —
   very small, a recognition marker only (reading/listening happen on the cards).
   Dropped clear of the descenders into the leading below the word, a thin dotted
   rule between them. Injected / removed client-side by the switcher; absent until
   a language different from the story's own is picked. */
.vocab-pill .pill-word-lang {
  position: absolute;
  left: 0;
  right: 0;                /* span the pill = the top word's width */
  top: 100%;
  margin-top: 2px;
  text-align: center;      /* centre the (usually shorter) practice word */
  white-space: nowrap;
  cursor: default;         /* tappable only when a stored mp3 exists (below) */
  padding-top: 4px;
  border-top: 1px dashed color-mix(in srgb, var(--teach-ink) 55%, transparent);
  font-family: var(--font-ui);
  font-weight: 800;
  font-size: 0.55em;
  letter-spacing: 0.02em;
  color: var(--teach-ink);
  opacity: 0.85;
}
.vocab-pill .pill-word-lang[data-audio] { cursor: pointer; }
.vocab-pill .pill-word-lang:hover { opacity: 1; }

.teaching-rehearsal { margin-top: 32px; }
.teaching-rehearsal .section-label {
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg2);
  margin-bottom: 10px;
}
/* Words-learning-language switcher (decision 172): lives INSIDE the "today's
   words" tray (.targets-tray, up top) so a reader can pick a practice language
   before they start reading — independent of the reader-language switcher
   further up the page. It rides INLINE with the word chips as a compact combo
   — a bare dropdown with a pencil affordance, sized to about a chip's height —
   so on a wide screen the words and this combo share one row; the tray's
   flex-wrap drops the combo to the next row only when space runs out. */
.word-lang-switcher {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}
/* Compact combo (decision 172 polish): no visible label, native arrow removed,
   a small pencil overlaid at the right edge signals "change the practice
   language". Sized ~80% of the old control and stripped of right padding. */
.word-lang-switcher__combo {
  position: relative;
  display: inline-flex;
  align-items: center;
}
.word-lang-switcher__select {
  font-size: 0.6rem;
  padding: 4px 17px 4px 5px;   /* right pad = just room for the pencil */
  border-radius: var(--radius-input);
  border: 1px solid var(--rule);
  background: var(--surface);
  color: var(--fg1);
  max-width: 11rem;
  line-height: 1.5;            /* room for ascenders + descenders of endonyms */
  cursor: pointer;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}
.word-lang-switcher__icon {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 11px;
  height: 11px;
  color: var(--fg3);
  pointer-events: none;   /* clicks fall through to the select → native menu */
}
/* The upgrade/sign-in tease is only shown to capped viewers (never on the
   showcase/pinned/owner path); cap its width so it wraps tidily under the
   compact combo instead of stretching it. */
.word-lang-switcher__tease {
  font-size: 0.76rem;
  color: var(--fg3);
  max-width: 16rem;
}
.word-lang-switcher__tease a { color: var(--accent-ink); font-weight: 700; }
.word-lang-switcher__select:disabled { opacity: 0.55; cursor: wait; }
/* Small spinner while a fetch to /teaching-words is in flight, toggled via
   the .is-loading class on the switcher (see the inline script below). */
/* While a /teaching-words fetch is in flight, the pencil gives way to a spinner
   sitting in the same spot. */
.word-lang-switcher.is-loading .word-lang-switcher__icon { display: none; }
.word-lang-switcher.is-loading .word-lang-switcher__combo::after {
  content: "";
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 10px;
  height: 10px;
  border: 1.5px solid var(--fg3);
  border-top-color: transparent;
  border-radius: 50%;
  animation: word-lang-spin 0.6s linear infinite;
}
@keyframes word-lang-spin {
  to { transform: rotate(360deg); }
}
/* Flip card: front = word + tap-to-hear, back = the story sentence the
   word appeared in. Click anywhere except the speaker button to flip. */
.vocab-cards-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  justify-content: center;
  align-items: flex-start;
}
@media (max-width: 500px) {
  .vocab-cards-row .flashcard-scene { flex: 1 1 100%; display: flex; justify-content: center; }
  /* On a phone the cards stack one-per-row, so it's fine (better, even) to let
     them grow to the column width — WORD AND CONCEPT alike, for one consistent
     feel. Full width up to a shared cap widens the lines so a long non-English
     back wraps less and a taller card means less scrolling; crucially it also
     stops the FIXED-width concept card (300/340px) and its quiz from spilling
     off a narrow screen (content area is only ~324px on a 360px phone). */
  .flashcard,
  .flashcard.concept-card,
  .flashcard.concept-card.has-payoff { width: 100%; max-width: 380px; }
  .flashcard:not(.concept-card) .face.front { min-height: 240px; }
}
.flashcard-scene { perspective: 900px; }
.flashcard {
  position: relative;
  width: 220px;
  cursor: pointer;
  transform-style: preserve-3d;
  transition: transform 0.5s;
}
.flashcard.flipped { transform: rotateY(180deg); }
/* The front face is in-flow and sizes the card; the back overlays it. */
/* Faces sit ON the reading panel (also var(--surface)), so they'd merge if
   filled the same. Use --surface-2 for a tonal step, a 2px gold frame on all
   four edges, and a stronger shadow so the card clearly lifts off the story. */
.flashcard .face {
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  background: var(--surface-2);
  border-radius: 14px;
  border: 2px solid var(--magic);
  padding: 14px;
  text-align: center;
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.flashcard .face.front { position: relative; min-height: 165px; }
.flashcard .face.back {
  position: absolute;
  inset: 0;
  transform: rotateY(180deg);
  overflow-y: auto;
  /* Top-align (not the inherited center): with justify-content:center a back
     taller than the card centres its content, pushing the TOP out of the
     scroll box where it can't be reached — so a long non-English back opened
     mid-text with the first lines hidden. flex-start keeps the top in view and
     the reader scrolls DOWN from there. Smooth touch scroll + overscroll
     containment so a swipe scrolls the card, not the page behind it. */
  justify-content: flex-start;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding-bottom: 18px;
}
.flashcard .face > i { color: var(--magic-deep); }
.flashcard .back-label {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg2);
  margin-bottom: 6px;
}
.flashcard .sentence { font-size: 0.86rem; line-height: 1.45; color: var(--fg1); }
.flashcard .sentence strong { color: var(--magic-deep); }
.flashcard .flip-hint { font-size: 0.7rem; color: var(--fg3); margin-top: 8px; }
.flashcard .word { font-size: 1.3rem; font-weight: 700; color: var(--magic-deep); margin: 6px 0 3px; }
.flashcard .hint { font-size: 0.8rem; color: var(--fg2); margin-bottom: 10px; }
.flashcard .say-btn {
  width: 36px; height: 36px;
  border-radius: 50%;
  border: none;
  background: var(--magic);
  color: #fff;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  display: inline-flex; align-items: center; justify-content: center;
}
/* An explicit display: overrides [hidden], so restore it: the switcher hides the
   speaker when a switched practice language has no stored mp3 (no browser-voice
   fallback for words languages — see design note). */
.flashcard .say-btn[hidden] { display: none; }
/* The speaker is a lucide <i data-lucide> that hydrates into an inline <svg>
   (the old FontAwesome glyph rendered as an empty coloured circle when the kit
   403'd — same reason the concept card moved to lucide). Size the svg so the
   speaker is centred and legible in the circle. */
.flashcard .say-btn svg { width: 18px; height: 18px; }
.flashcard .native-line .say-btn svg { width: 15px; height: 15px; }
.spell-hear-inline svg { width: 16px; height: 16px; }
.flashcard .stars { margin-top: 10px; color: var(--magic-deep); font-size: 0.9rem; }
.flashcard .stars .fa-star.empty { color: var(--rule); }
.flashcard .seen-in { font-size: 0.8rem; color: var(--fg2); margin-top: 4px; }

/* --- Narration player (decision 31). Mirrors the design-system .ds-audiobar
   (see /styleguide): a dark ink pill with a coral play button and a gold
   progress line — legible in light, sepia AND dark (dark mode flips to a raised
   surface so the pill never disappears). Kept as its own .np-* component rather
   than literally reusing .ds-audiobar because it carries the read-along SPEED
   slider the styleguide bar doesn't, and the player JS hooks onto these class
   names — so the look is shared, the markup/behaviour preserved. */
.narration-player {
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 600px;
  margin: 0 auto 16px;
  padding: 8px 14px 8px 8px;   /* slim: the bar's job is to play, not to dominate */
  border-radius: var(--radius-chip);
  background: var(--fg1);
  color: var(--bg);
  box-shadow: var(--shadow-lg);
}
[data-mode="dark"] .narration-player { background: var(--surface-2); color: var(--fg1); }

.narration-player .np-toggle {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  border-radius: 50%;
  border: none;
  background: var(--magic);   /* gold, matching the progress line */
  color: var(--fg1);          /* dark glyph reads on the gold disc... */
  cursor: pointer;
  display: grid;
  place-items: center;
}
/* ...and stays dark on gold in dark mode, where --fg1 would otherwise be light. */
[data-mode="dark"] .narration-player .np-toggle { color: var(--bg); }
.narration-player .np-toggle svg { width: 18px; height: 18px; }
.narration-player .np-toggle:hover { filter: brightness(1.05); }
.narration-player .np-body { flex: 1; min-width: 0; }
.narration-player .np-label {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.65;
  margin-bottom: 3px;
}
.narration-player .np-track {
  position: relative;
  height: 10px;          /* slimmer bar, still a comfortable seek hit area */
  cursor: pointer;
  display: flex;
  align-items: center;
}
.narration-player .np-track::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 4px;
  border-radius: 2px;
  /* light translucent groove on the dark pill (light/sepia); dark rule in dark
     mode where the pill is a raised surface — mirrors .ds-audiobar. */
  background: color-mix(in srgb, var(--bg) 24%, transparent);
}
[data-mode="dark"] .narration-player .np-track::before { background: var(--rule); }
.narration-player .np-progress {
  position: absolute;
  left: 0;
  width: 0%;
  height: 4px;
  border-radius: 2px;
  background: var(--magic);
  pointer-events: none;
}
/* First-view "getting ready" state (decision 91): occupies the same height the
   waveform will, so revealing the finished bar doesn't shift the layout. */
.narration-player .np-prep {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 38px;
  font-size: 0.8rem;
  opacity: 0.8;
}
.narration-player .np-prep .np-prep-dot {
  width: 8px; height: 8px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--magic);
  animation: np-prep-pulse 1s ease-in-out infinite;
}
@keyframes np-prep-pulse {
  0%, 100% { opacity: 0.3; transform: scale(0.8); }
  50%      { opacity: 1;   transform: scale(1.15); }
}
@media (prefers-reduced-motion: reduce) {
  .narration-player .np-prep .np-prep-dot { animation: none; opacity: 0.7; }
}

/* WaveSurfer waveform container in the family player */
.fp-waveform {
  margin-bottom: 4px;
  border-radius: 4px;
  overflow: hidden;
  cursor: pointer;
}
.narration-player .np-time {
  flex: 0 0 auto;
  font-size: 0.78rem;
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}
/* Read-along speed: a quiet drag bar under the progress line. Client-side
   playbackRate on the stored mp3 (pitch is preserved by the browser) —
   the generate-once asset never changes. */
.narration-player .np-speed {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 5px;
}
.narration-player .np-speed-label {
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.6;
}
.narration-player .np-speed-range {
  flex: 1;
  max-width: 160px;
  height: 14px;
  margin: 0;
  accent-color: var(--magic);
  cursor: pointer;
}
.narration-player .np-speed-value {
  font-size: 0.78rem;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
  min-width: 38px;
}

/* --- Narration source toggle (AI voice / Our family) --------------------- */
.np-source-toggle {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  max-width: 600px;
  margin: 0 auto 14px;
}
.nst-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 16px;
  border: 2px solid var(--rule);
  border-radius: 20px;
  background: var(--surface-2);
  color: var(--fg2);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.nst-btn.active {
  border-color: var(--magic);
  background: var(--magic-soft);
  color: var(--magic-deep);
}
.nst-btn:hover:not(.active) { border-color: var(--fg2); }
.nst-btn svg { width: 13px; height: 13px; }

/* NOTE: the old standalone ".family-player" WaveSurfer block was removed
   (2026-06) — the family player now reuses the .narration-player pill (same
   markup/look as the AI player) with IDs for JS, so those .fp-* class rules
   matched nothing AND a stale .fp-waveform here was shadowing the live compact
   one above (line ~410). The waveform container is now styled once, above. */

/* --- Family narration CTA (record/upload prompt) ------------------------ */
.np-family-cta { max-width: 600px; margin: 0 auto; }

/* Heritage line on the flashcard back: the word in the child's
   word-learning language, with stored pronunciation when available. */
.flashcard .native-line {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--rule);
  font-size: 0.95rem;
  color: var(--fg1);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
}
.flashcard .native-line .native-lang {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg2);
}
.flashcard .native-line .native-word { font-weight: 700; color: var(--magic-deep); }
.flashcard .native-line .native-translit { color: var(--fg2); font-size: 0.85rem; }
.flashcard .native-line .say-btn { width: 32px; height: 32px; font-size: 0.8rem; }
/* On the card FRONT (both words together, a speaker beside each) the
   heritage row sits tight under the main word — lighter divider, less air. */
.flashcard .native-line.front-native {
  margin-top: 6px;
  padding-top: 6px;
  border-top: none;
  font-size: 1.02rem;
}
/* The card back's English side: pivot word + the atom's kid-friendly meaning. */
.flashcard .english-line {
  margin-top: 10px;
  padding-top: 8px;
  border-top: 1px dashed var(--rule);
  font-size: 0.9rem;
  color: var(--fg1);
}
.flashcard .english-line .native-lang { display: block; margin-bottom: 2px; }
.flashcard .english-line strong { color: var(--magic-deep); }

/* --- Concept teaching layer (decision 33). A concept is a full sentence /
   idea, not a literal word, so: a wrapping "Today's idea" banner (no pills,
   no in-text highlight) and a wider flip card. Reuses .flashcard mechanics. */
.idea-tray { flex-direction: column; align-items: stretch; gap: 8px; }
.idea-tray .targets-label { color: var(--fg2); }
/* The idea sits in its own card (peach-accented) so the full sentence reads
   with strong contrast — the faint tray alone washed it out. */
.idea-chip {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  font-size: 1.02rem; font-weight: 600; color: var(--fg1); line-height: 1.5;
  background: var(--surface);
  border-left: 4px solid var(--magic);
  border-radius: 12px;
  padding: 12px 16px;
  box-shadow: var(--shadow-sm);
}
.idea-chip > i { color: var(--magic-deep); font-size: 1rem; }
.subject-pill {
  display: inline-block; border-radius: 999px; padding: 1px 9px;
  font-size: 0.72rem; font-weight: 600; white-space: nowrap;
}
/* Subject pills are category signals (not brand surface) — kept literal. */
.subject-pill.science { background: #e6f3ff; color: #1d63a8; }
.subject-pill.math { background: #eef0ff; color: #4338a8; }
.flashcard.concept-card { width: 300px; }
.flashcard.concept-card .face.front { min-height: 200px; }
/* The payoff back (definition + anchor + quiz + grown-up note) needs more
   room than the plain curriculum card, so a summary card is taller/wider —
   the back overlays the front, so the front sizes the card (feature §5). */
.flashcard.concept-card.has-payoff { width: 340px; }
.flashcard.concept-card.has-payoff .face.front { min-height: 380px; }
.flashcard.concept-card.has-payoff .face.back { justify-content: flex-start; }
.flashcard .concept-text { font-size: 1.02rem; font-weight: 600; color: var(--fg1); line-height: 1.45; margin: 10px 0; }
/* Idea bulb + speaker use lucide (see template comment) — size/color them
   explicitly since the shared ".flashcard .face > i" rule only targets the
   FontAwesome <i> tag, not the <svg> lucide swaps it for. */
.concept-card .idea-bulb-icon { width: 32px; height: 32px; color: #F5B942; margin-bottom: 2px; }
.concept-card .say-btn svg { width: 16px; height: 16px; }
/* Front-face flip hint: bigger and gently blinking so the quiz payoff on
   the back doesn't get missed (feature ask: draw attention to it). */
.concept-card .face.front .flip-hint {
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--magic-deep);
  margin-top: 10px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  animation: concept-flip-hint-pulse 1.6s ease-in-out infinite;
}
.concept-card .face.front .flip-hint svg { width: 15px; height: 15px; }
@keyframes concept-flip-hint-pulse {
  0%, 100% { opacity: 0.5; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .concept-card .face.front .flip-hint { animation: none; opacity: 0.9; }
}
.flashcard .detail-row { font-size: 0.86rem; color: var(--fg1); margin: 4px 0; text-align: left; width: 100%; }
.flashcard .detail-row .k { color: var(--fg2); margin-right: 6px; }

/* --- Concept payoff card (feature §5): kid definition, anchor callback,
   'spot it' quiz, and the collapsed grown-up note. Peach identity reused. */
.flashcard .kid-def {
  font-size: 0.98rem; font-weight: 600; color: var(--fg1); line-height: 1.45;
  margin: 2px 0 10px; text-align: center;
}
.flashcard .anchor-callback {
  display: flex; gap: 7px; align-items: flex-start;
  font-size: 0.86rem; line-height: 1.4; color: var(--fg2);
  background: var(--magic-soft);
  border-radius: 10px; padding: 8px 10px; margin-bottom: 12px;
  text-align: left; width: 100%;
}
.flashcard .anchor-callback > i { color: var(--magic-deep); margin-top: 2px; }
/* Front-side anchor sits under the idea, above the say button — a touch
   more breathing room than the old back-face placement needed. */
.concept-card .face.front .anchor-callback--front { margin: 4px 0 12px; }
.flashcard .spot-it-quiz { width: 100%; margin-bottom: 10px; }
/* Back-face quiz is the payoff moment — sized up from the base flashcard
   scale so it reads as the main event, not fine print. */
.flashcard .quiz-q {
  font-size: 1.02rem; font-weight: 700; color: var(--fg1);
  margin-bottom: 10px; text-align: center;
  overflow-wrap: break-word;
}
.flashcard .quiz-options { display: flex; flex-direction: column; gap: 7px; }
.flashcard .quiz-option {
  font-size: 0.96rem; color: var(--fg1); text-align: left;
  background: var(--surface); border: 1.5px solid var(--rule); border-radius: 10px;
  padding: 10px 13px; cursor: pointer; transition: background 0.15s, border-color 0.15s;
  /* Options are buttons; let a long option (or a long unbroken word) wrap
     instead of forcing the card wider than a narrow phone. */
  white-space: normal; overflow-wrap: break-word;
}
.flashcard .quiz-option:hover:not(.locked) { border-color: var(--magic); }
/* right/wrong = semantic green/red, kept literal across themes. */
.flashcard .quiz-option.correct {
  background: #e7f6ea; border-color: #6fbf7e; color: #1f6f33; font-weight: 600;
}
.flashcard .quiz-option.incorrect {
  background: #fdeaea; border-color: #e08a8a; color: #9a3030;
}
.flashcard .quiz-option.locked { cursor: default; }
@keyframes quiz-celebrate {
  0%   { transform: scale(1); }
  30%  { transform: scale(1.12); }
  60%  { transform: scale(0.96); }
  100% { transform: scale(1); }
}
.flashcard .quiz-feedback {
  font-size: 0.84rem; font-weight: 600; margin-top: 8px; min-height: 1em; text-align: center;
}
.flashcard .quiz-feedback.right {
  color: #1a6b2f; font-weight: 700; font-size: 1rem; letter-spacing: 0.01em;
  animation: quiz-celebrate 0.45s ease;
}
.flashcard .quiz-feedback.wrong { color: #9a3030; }
@media (prefers-reduced-motion: reduce) {
  .flashcard .quiz-feedback.right { animation: none; }
}

/* --- "Ask the story" — gentle end-of-story comprehension look-back (decision
   86). A calm card list, NOT a quiz/score screen: plain questions, tappable
   options, instant feedback with one retry, never blocking. Reuses the spot-it
   quiz look but stands as its own end-of-story section. --- */
.ask-the-story { margin-top: 32px; }
.ask-the-story .section-label {
  font-size: 0.78rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--fg2); margin-bottom: 12px; text-align: center;
}
.story-question {
  background: var(--surface); border: 1.5px solid var(--rule); border-radius: 14px;
  padding: 16px 16px 14px; margin: 0 auto 14px; max-width: 480px;
}
.story-question .sq-q {
  font-size: 0.98rem; font-weight: 600; color: var(--fg1);
  margin-bottom: 12px; text-align: center; line-height: 1.35;
}
.story-question .sq-options { display: flex; flex-direction: column; gap: 8px; }
.story-question .sq-option {
  font-size: 0.92rem; color: var(--fg1); text-align: left;
  background: var(--surface-2); border: 1.5px solid var(--rule); border-radius: 11px;
  padding: 10px 14px; cursor: pointer; transition: background 0.15s, border-color 0.15s;
  white-space: normal; overflow-wrap: break-word;
}
.story-question .sq-option:hover:not(.locked) { border-color: var(--magic); }
/* right/wrong = semantic green/red, kept literal across themes (mirrors spot-it). */
.story-question .sq-option.correct {
  background: #e7f6ea; border-color: #6fbf7e; color: #1f6f33; font-weight: 600;
}
.story-question .sq-option.incorrect {
  background: #fdeaea; border-color: #e08a8a; color: #9a3030;
}
.story-question .sq-option.locked { cursor: default; }
.story-question .sq-feedback {
  font-size: 0.86rem; font-weight: 600; margin-top: 10px; min-height: 1.2em; text-align: center;
}
.story-question .sq-feedback.right {
  color: #1a6b2f; font-weight: 700; animation: quiz-celebrate 0.45s ease;
}
.story-question .sq-feedback.wrong { color: #9a3030; }
.story-question .sq-anchor {
  display: flex; gap: 7px; align-items: flex-start;
  font-size: 0.88rem; line-height: 1.4; color: var(--fg2);
  background: var(--magic-soft); border-radius: 10px; padding: 9px 11px;
  margin-top: 10px; text-align: left;
}
.story-question .sq-anchor > i { color: var(--magic-deep); margin-top: 2px; }

.flashcard .grownup-corner {
  width: 100%; text-align: left; margin-top: 4px;
  border-top: 1px solid var(--rule); padding-top: 8px;
}
.flashcard .grownup-corner > summary {
  font-size: 0.82rem; font-weight: 600; color: var(--fg2); cursor: pointer;
  list-style: none; display: flex; align-items: center; gap: 6px;
}
.flashcard .grownup-corner > summary::-webkit-details-marker { display: none; }
.flashcard .grownup-corner > summary > i { color: var(--fg3); }
.flashcard .grownup-note {
  font-size: 0.84rem; line-height: 1.45; color: var(--fg2); margin-top: 6px;
}

/* --- Read-along highlight ---------------------------------------- */
.ra-word { border-radius: 3px; transition: background 0.1s, color 0.1s; }
.ra-word.ra-active { background: var(--magic-soft); color: var(--magic-deep); padding: 0 1px; }
.ra-word.ra-active-near { background: var(--rule-soft); padding: 0 1px; }

.readalong-bar { display: none; justify-content: center; margin: 10px 0 14px; }
.btn-readalong {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--surface); border: 1.5px solid var(--rule);
  border-radius: 20px; padding: 7px 20px;
  font-size: 0.87rem; font-weight: 600; color: var(--fg1);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.15s, background 0.15s, border-color 0.15s;
}
.btn-readalong:hover { box-shadow: var(--shadow-md); }
.btn-readalong.active { background: var(--magic-soft); border-color: var(--magic); color: var(--magic-deep); }

/* --- Reader settings: "Aa" text-size control. Parked beside the story
   title — position:absolute against .story-title-row, vertically centred
   on the title — rather than inline in a flex row with other controls, so
   opening/closing the panel below it never reflows or shifts anything
   else (a flex sibling that grows taller was visibly "jumping" the whole
   row before this). Glyph is plain styled TEXT ("Aa", "−", "+"), not an
   icon font: this project's Font Awesome kit doesn't render every icon
   (fa-text-height/fa-minus/fa-plus all came back blank), and plain text
   can never fail to load. Deliberately not the wheelchair/accessibility
   symbol either — see design decision 116. Coloured with --accent-ink and
   a visible tinted border (not the near-invisible --rule alone) so it
   reads clearly against the light and sepia backgrounds, not just dark. --- */
/* z-index lifts the WHOLE title-row subtree (including the absolutely-positioned
   reader-settings panel) above the later scene blocks. Without it the panel,
   nested here in DOM order before the scenes, paints UNDER the scene images —
   the "Aa panel hides behind the picture" report. */
.story-title-row { position: relative; z-index: 20; }
/* Base: position:relative so .reader-settings-body (absolute) always anchors
   to THIS element, wherever it's placed (e.g. the /styleguide specimen,
   which isn't inside .story-title-row). On the actual reading page the more
   specific rule below overrides to position:absolute to park it beside the
   title instead. */
.reader-settings { position: relative; }
.story-title-row .reader-settings {
  position: absolute; top: 50%; right: 2px; transform: translateY(-50%); margin: 0;
}
.reader-settings > summary {
  list-style: none; cursor: pointer;
  width: 38px; height: 38px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--accent-ink);
  /* Soft accent FILL (not plain surface) so the control doesn't vanish against
     the white story card — it now reads as a tappable chip. A slightly firmer
     tinted ring adds definition without shouting. Open/hover still flips to the
     solid accent below. */
  border: 1.5px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: var(--accent-soft);
  box-shadow: var(--shadow-sm);
  transition: color 0.15s, border-color 0.15s, background 0.15s, transform 0.1s;
}
/* tactile press feedback (matters on touch, where there's no hover cue) */
.reader-settings > summary:active { transform: scale(0.93); }
.reader-settings > summary::-webkit-details-marker { display: none; }
/* Solid accent only while the panel is OPEN. Hover is gated to hover-capable
   pointers (mice) — on touch, :hover "sticks" after a tap and the button used
   to stay orange even after the panel collapsed (reported on-device). */
.reader-settings[open] > summary { color: #fff; background: var(--accent-deep); border-color: var(--accent-deep); }
@media (hover: hover) {
  .reader-settings > summary:hover { color: #fff; background: var(--accent-deep); border-color: var(--accent-deep); }
}
.rs-aa { font-family: var(--font-ui, 'Nunito'), system-ui, sans-serif; font-weight: 800; font-size: 1rem; line-height: 1; }
.rs-aa span { font-size: 0.7em; margin-left: 1px; }
.reader-settings-body {
  position: absolute; right: 0; top: calc(100% + 10px);
  padding: 16px 18px;
  background: var(--surface); border: 1px solid var(--rule); border-radius: 14px;
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 16px;
  min-width: 240px; max-width: 320px; z-index: 60;
}
.rs-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.rs-row .rs-label { font-size: 0.86rem; font-weight: 600; color: var(--fg1); }
.rs-stepper { display: flex; align-items: center; gap: 8px; }
.rs-stepper button {
  width: 30px; height: 30px; border-radius: 8px; border: 1.5px solid var(--rule);
  background: var(--surface-2); color: var(--fg1); font-weight: 800; cursor: pointer;
  display: grid; place-items: center; font-size: 1.05rem; line-height: 1;
  transition: border-color 0.15s, color 0.15s;
}
.rs-stepper button:hover:not(:disabled) { border-color: var(--accent-soft); color: var(--accent-ink); }
.rs-stepper button:disabled { opacity: 0.4; cursor: default; }
.rs-stepper .rs-size-label { font-size: 0.78rem; color: var(--fg2); min-width: 60px; text-align: center; }
/* role="switch" button (not a checkbox) — a native on/off control that
   reads correctly to screen readers via aria-checked. */
.rs-switch {
  width: 42px; height: 24px; border-radius: 999px; border: 1px solid var(--rule);
  background: var(--surface-2); position: relative; cursor: pointer; padding: 0; flex-shrink: 0;
  transition: background 0.15s, border-color 0.15s;
}
.rs-switch::after {
  content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px;
  border-radius: 50%; background: var(--fg3);
  transition: transform 0.15s, background 0.15s;
}
.rs-switch[aria-checked="true"] { background: var(--accent-soft); border-color: var(--accent-ink); }
.rs-switch[aria-checked="true"]::after { transform: translateX(18px); background: var(--accent-ink); }
@media (prefers-reduced-motion: reduce) {
  .rs-switch, .rs-switch::after { transition: none; }
}
/* Phone: the "Aa" reader-settings control is position:absolute at the title
   row's right edge, so a centered title used to run under it (the last word
   overlapped the circle — see the "An elephant and a frog" report). Left-align
   and shrink the title (long ones wrap anyway, so smaller reads better), pin
   the control to the top-right instead of vertically-centered over a wrapped
   title, and reserve a right gutter so every line clears it. */
@media (max-width: 600px) {
  .story-title,
  .read-body.toddler .story-title {
    text-align: left !important;
    margin-left: 0; margin-right: 0;
    padding-left: 0; padding-right: 46px;
  }
  .story-title { font-size: clamp(1.35rem, 6vw, 1.7rem); }
  .read-body.toddler .story-title { font-size: clamp(1.7rem, 7vw, 2.1rem); }
  .story-title-row .reader-settings { top: 4px; transform: none; }
  .story-reads { text-align: left !important; }
}

/* --- Spelling minigame ------------------------------------------- */
.spelling-game { margin-top: 2rem; }
.spell-cards-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  justify-content: center;
  align-items: flex-start;
}
.spell-card {
  background: var(--surface-2);
  border-radius: 14px;
  border: 2px solid var(--magic);
  box-shadow: var(--shadow-md);
  padding: 1rem 1.1rem;
  flex: 1 1 190px;
  max-width: 240px;
  text-align: center;
}
@media (max-width: 500px) {
  .spell-card { flex: 1 1 100%; max-width: 360px; }
}
.spell-card .spell-title {
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em;
  color: var(--fg2); text-transform: uppercase; margin-bottom: 0.8rem;
}
.spell-hint {
  display: flex; justify-content: center; align-items: baseline;
  gap: 5px; margin: 0.4rem 0 0.9rem; flex-wrap: wrap;
}
.hint-letter {
  font-size: 1.3rem; font-weight: 700; color: var(--magic-deep);
  border-bottom: 3px solid var(--magic-deep); padding-bottom: 2px;
  min-width: 1.1ch; text-align: center;
}
.hint-dash {
  font-size: 1.3rem; font-weight: 700; color: var(--fg3);
  border-bottom: 3px solid var(--fg3); padding-bottom: 2px;
  min-width: 1.1ch; text-align: center;
}
.spell-input {
  width: 100%; max-width: 190px;
  padding: 8px 12px; font-size: 1rem; text-align: center;
  border: 2px solid var(--rule); border-radius: 12px;
  background: var(--surface-2);
  color: var(--fg1); outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
  display: block; margin: 0 auto;
}
.spell-input:focus { border-color: var(--magic); box-shadow: 0 0 0 3px var(--magic-soft); }
.spell-input.correct { border-color: #2a9d4e; box-shadow: 0 0 0 3px rgba(42,157,78,0.2); }
.spell-input.wrong   { border-color: #c94040; box-shadow: 0 0 0 3px rgba(201,64,64,0.18); animation: spell-shake 0.35s; }
@media (prefers-reduced-motion: reduce) {
  .spell-input.wrong { animation: none; }
}
@keyframes spell-shake {
  0%,100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(4px); }
}
.spell-row {
  display: flex; justify-content: center; align-items: center;
  gap: 8px; margin-top: 0.7rem;
}
.spell-check-btn {
  background: var(--magic); border: none; border-radius: 10px;
  padding: 8px 22px; font-size: 0.9rem; font-weight: 700;
  color: #fff; cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: opacity 0.15s;
}
.spell-check-btn:hover { opacity: 0.88; }
.spell-check-btn:disabled { opacity: 0.45; cursor: default; }
.spell-hear-inline {
  background: none; border: 1.5px solid var(--rule); border-radius: 50%;
  width: 36px; height: 36px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.9rem; color: var(--fg2); cursor: pointer;
}
.spell-hear-inline:hover { border-color: var(--magic); color: var(--magic-deep); }
.spell-feedback {
  font-size: 0.9rem; font-weight: 600; margin-top: 0.55rem; min-height: 1.3em;
  color: var(--fg2);
}
.spell-feedback.right { color: #1f6f33; }
.spell-feedback.wrong { color: #9a3030; }
.spell-reveal {
  margin-top: 0.8rem; padding: 0.55rem 1rem;
  background: var(--magic-soft); border-radius: 10px; border: 1.5px solid var(--magic);
}
.spell-reveal .reveal-label { font-size: 0.78rem; color: var(--fg2); }
.spell-reveal .reveal-word {
  display: block; font-size: 1.5rem; font-weight: 700; color: var(--magic-deep); margin-top: 2px;
}

/* --- Audio make-zone (declutter 2026-06): the owner's "voice options" by the
   player, or the quiet "Add a narration" slot when a story has no audio yet.
   Subtle by default; the --add variant is a touch more inviting (gold, never
   coral) since it sits in the empty audio slot under the title. Token-only. --- */
.voice-menu { max-width: 600px; margin: -4px auto 18px; }
.voice-menu > summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 0.78rem; color: var(--fg3); font-weight: 600;
  padding: 5px 13px; border-radius: 999px;
  border: 1px solid var(--rule); background: var(--surface-2);
  transition: color 0.15s, border-color 0.15s;
}
.voice-menu > summary::-webkit-details-marker { display: none; }
.voice-menu > summary i { font-size: 0.8rem; opacity: 0.8; }
.voice-menu > summary:hover,
.voice-menu[open] > summary { color: var(--fg2); border-color: var(--fg3); }
.voice-menu-body { margin-top: 10px; display: flex; flex-direction: column; gap: 12px; }
/* No-audio variant: a soft, full-width, gently inviting prompt in the audio slot. */
.voice-menu--add { margin-top: 0; }
.voice-menu--add > summary {
  width: 100%; justify-content: center;
  font-size: 0.85rem; color: var(--magic-deep);
  background: var(--magic-soft); border-color: var(--magic);
}
.voice-menu--add > summary i { opacity: 1; color: var(--magic-deep); }
.voice-menu--add > summary:hover,
.voice-menu--add[open] > summary { color: var(--magic-deep); border-color: var(--magic-deep); }

/* --- "After you finish" divider (declutter 2026-06): a faint centred seam that
   closes the story and opens the calm, secondary post-story zone (rehearsal
   cards, reactions, discussion). Token-only; no brand colour. --- */
.after-divider {
  display: flex; align-items: center; gap: 14px;
  margin: 34px 0 24px; color: var(--fg3);
}
.after-divider::before, .after-divider::after {
  content: ""; flex: 1; height: 1px; background: var(--rule);
}
.after-divider span {
  font-size: 0.72rem; letter-spacing: 0.1em; text-transform: uppercase; white-space: nowrap;
}

/* --- "Story tools" drawer (declutter 2026-06): owner make-controls (generate
   narration, record family narration, share) collapsed by default so they never
   crowd the reading. Deliberately quiet neutral chrome — the page's emphasis
   belongs to the story and its one coral action, not these. --- */
.story-tools {
  max-width: 600px;
  margin: 28px auto 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
  background: var(--surface-2);
}
.story-tools > summary {
  list-style: none;
  cursor: pointer;
  padding: 12px 18px;
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--fg2);
  display: flex;
  align-items: center;
  gap: 6px;
}
.story-tools > summary::-webkit-details-marker { display: none; }
.story-tools > summary::after {
  content: "\203A";              /* › chevron */
  margin-left: auto;
  transform: rotate(90deg);
  transition: transform 0.2s;
  font-size: 1.1rem;
  color: var(--fg3);
}
.story-tools[open] > summary::after { transform: rotate(-90deg); }
.story-tools[open] > summary { border-bottom: 1px solid var(--rule); }
.story-tools-body { padding: 4px 18px 14px; }
.story-tools .st-section { padding: 14px 0; border-top: 1px solid var(--rule); }
.story-tools .st-section:first-child { border-top: none; }
.story-tools .st-label {
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--fg3); font-weight: 700; margin-bottom: 8px;
}

/* Locked translations (count-based tier gate, 2026-08-20): still a pill, just
   dimmed with a small lock so the reader sees the language exists but that it's
   beyond their plan. Clicking sends anon -> sign in, signed-in -> upgrade. */
.reader-langs .reader-lang--locked {
  opacity: 0.6;
  display: inline-flex; align-items: center; gap: 4px;
}
.reader-langs .reader-lang--locked:hover { opacity: 0.85; }
.reader-lang__lock { flex: 0 0 auto; }
.reader-langs__note {
  margin: 8px 0 0; font-size: 0.8rem; color: var(--fg3);
}
.reader-langs__note a { color: var(--accent-ink); font-weight: 700; }

/* --- Reader language switcher (story_detail): the row of "read in another
   language" pills. On a phone a story with all 15 versions was pushing the
   story itself below the fold, so on narrow screens we DON'T scroll sideways —
   we shrink the pills and let them WRAP into a few compact rows, keeping the
   whole set visible while giving the title/first image back its space. --- */
@media (max-width: 700px) {
  /* Left-align the wrapped rows so the first pill of every row shares one left
     edge (was centered, which staggered each row's start). */
  .reader-langs {
    column-gap: 5px !important;
    row-gap: 10px !important;   /* more air between the wrapped rows = bigger tap targets */
    justify-content: flex-start !important;
    align-content: flex-start;
    width: 100%;
  }
  /* Tighten the space from the last pill row down to the today's-word / idea
     section (the switcher wrapper's mb-4 is the gap that collapses in here). */
  .text-center:has(> .reader-langs) { margin-bottom: 8px !important; }
  .reader-langs .ds-pill,
  .reader-langs .ds-btn {
    padding: 3px 9px;
    font-size: 0.72rem;
    line-height: 1.15;
    border-radius: 999px;
    min-height: 0;
  }
}
@media (max-width: 400px) {
  .reader-langs .ds-pill,
  .reader-langs .ds-btn { padding: 2px 7px; font-size: 0.68rem; }
}

/* --- Teaching-word chips (.targets-tray) on a phone: tighter tray padding and
   smaller chips. The helper-text-on-its-own-row layout is now the common design
   for all screens (see the base .targets-tray:not(.idea-tray) .targets-label
   rule), so only the phone-specific sizing lives here. --- */
@media (max-width: 700px) {
  .targets-tray:not(.idea-tray) { gap: 3px 6px; padding: 8px 12px; margin-bottom: 16px; }
  .target-chip { padding: 3px 10px; font-size: 0.8rem; gap: 5px; }
  .target-chip i { font-size: 0.72rem; }
  .target-chip .chip-say { width: 18px; height: 18px; }
  .target-chip .chip-say svg { width: 13px; height: 13px; }
}
@media (max-width: 400px) {
  .target-chip { padding: 2px 8px; font-size: 0.76rem; gap: 4px; }
}
