/* Self-hosted (OFL-licensed) -- no CDN, so this still works fully offline
   after first load, per Part B's PWA requirements. */
@font-face {
  font-family: 'Baloo 2';
  src: url('fonts/Baloo2-Regular.ttf') format('truetype');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Baloo 2';
  src: url('fonts/Baloo2-Bold.ttf') format('truetype');
  font-weight: 700;
  font-display: swap;
}
/* Self-hosted (OFL-licensed), same offline-first reasoning as Baloo 2 above
   -- a Google Fonts <link> would silently break on a cold offline start
   (the service worker only precaches same-origin assets). Reading-text
   face: Andika's single-storey a/g match how kids learn to write them.
   Baloo 2 stays the face for headings/numbers/buttons -- see the grouped
   reading-text override below, not a global font-family flip, so the much
   larger set of Baloo-2 button/heading classes needs no individual change. */
@font-face {
  font-family: 'Andika';
  src: url('fonts/Andika-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Andika';
  src: url('fonts/Andika-Bold.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}

/* Dark mode was deliberately removed (not just left unhandled) after three
   separate real bugs surfaced there in a row -- a washed-out palette, two
   illegible-text cases, and a color that nearly matched one of NNAT's own
   puzzle-attribute colors. Maintaining a second fully-designed, fully-tested
   palette was pure QA surface for a feature nobody asked for: "dark mode
   aware" was generic boilerplate in the original UX requirements, not
   something tied to how Shail actually uses this. One deliberately-designed
   bright palette, always, regardless of system theme -- color-scheme:light
   below keeps native browser chrome (scrollbars etc.) consistent with it. */
/* Palette v2 (Oct 2026): warm-paper/navy/orange system from the Claude
   Design redesign pass ("Practice App v2"), reusing these SAME variable
   names (not renamed) so the ~140 existing `var(--color-*)` usages across
   this file repaint automatically -- see docs/design note in the plan file
   for the full redesign scope. New tokens added where the redesign needs a
   genuinely new concept (white card surface, muted ink, a border tint, a
   blue "info" accent) that didn't exist in the old palette. */
:root {
  color-scheme: light;
  --color-bg: #e9e4d9;
  --color-surface: #ffffff;
  --color-primary: #d95a22;
  --color-primary-shadow: #a9441a;
  --color-secondary: #ffffff;
  --color-accent: #ffffff;
  --color-border: #e6dfd0;
  --color-info: #2b6fd6;
  --color-success: #17936a;
  --color-success-bg: #e2f4ec;
  --color-success-text: #0f6b4c;
  --color-retry: #fdf3dc;
  --color-retry-border: #e8b54a;
  --color-info-bg: #e6effc;
  --color-peach: #fbe3d6;
  --color-peach-text: #9a3d12;
  --color-text: #1f2238;
  --color-text-muted: #5a5e72;
  --color-feedback-correct: #0f6b4c;
  --color-feedback-retry: #6e4a00;
  --shadow-opacity: 0.12;
  font-family: 'Baloo 2', 'Comic Sans MS', ui-rounded, system-ui, sans-serif;
}

/* Reading-text override: Andika, not Baloo 2, for the classes that render
   text a kid actually READS (lesson/fact/hint/feedback copy, parent-
   dashboard prose) rather than headings/numbers/button labels. A grouped
   override here is far smaller than re-asserting Baloo 2 on the much
   longer list of heading/number/button classes if the default were flipped
   the other way at :root. */
.teach-text,
.teach-hook,
.teach-reveal,
.teach-question,
.teach-check-feedback,
.fact-prompt,
.barmodel-text,
.coins-text,
.clock-text,
.fun-fact-text,
.trick-text,
.hint-btn,
.feedback,
.skip-confirm-text,
.rank-progress,
.mastered,
.parent-item-meta,
.parent-item-encourage,
.parent-subject-meta,
.parent-totals,
.parent-empty,
.parent-week-headline {
  font-family: 'Andika', 'Comic Sans MS', ui-rounded, system-ui, sans-serif;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--color-bg);
  color: var(--color-text);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: max(28px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
    max(16px, env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
}

#app {
  width: 100%;
  max-width: 720px;
}

.session-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
  padding-bottom: 16px;
  font-size: 14px;
}

.exit-btn {
  background: transparent;
  border: none;
  color: var(--color-text);
  font-size: 16px;
  font-weight: bold;
  cursor: pointer;
  padding: 12px 14px;
  min-height: 48px;
  border-radius: 12px;
}

.exit-btn:active {
  background: rgba(0, 0, 0, 0.08);
}

.session-concept {
  opacity: 0.75;
  font-weight: bold;
}

.session-progress {
  opacity: 0.6;
}

/* No justify-content before this meant a short screen (a 2-line teach hook,
   a single-question NNAT puzzle) just pinned to the top of the viewport,
   leaving large dead space below on anything shorter than a full page --
   live audits at both phone and tablet width caught this independently.
   min-height gives justify-content:center something to center WITHIN; on a
   screen whose content is actually taller than that (already overflowing),
   the box just grows past it and this has no effect, so long content is
   unaffected. ~60px approximates body's own top+bottom padding below. */
.screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  text-align: center;
  width: 100%;
  min-height: calc(100vh - 60px);
  min-height: calc(100dvh - 60px);
}

/* On every question/teach screen, .session-header (the "<- Home" row) sits
   as .screen's own sibling, ABOVE it -- the ~60px above only accounts for
   body's padding, not that extra ~64px (session-header's own exit-btn row +
   padding-bottom). Real, measured consequence: every one of those screens
   overflowed the viewport by roughly session-header's height even when its
   own content was short enough to fit, permanently triggering the "more
   below" scroll cue over content that wasn't actually there -- a real
   screenshot-caught bug, not a hypothetical. */
.session-header + .screen {
  min-height: calc(100vh - 124px);
  min-height: calc(100dvh - 124px);
}

/* The drill-question screens show a small corner mascot (added this pass --
   he previously only appeared on teach/fact/reveal screens). A real,
   screenshot-caught bug went through two wrong fixes before this one:
   (1) absolutely-positioned mascot overlapped the mode-tag's own text when
   the tag wrapped to 2 lines; (2) a 2-item flex row (mode-tag flex:1 +
   mascot) fixed the overlap, but gave the mode-tag an ASYMMETRIC box --
   full width minus the mascot's footprint on the right ONLY -- so even
   perfectly-centered text inside that box sat visibly left of the page's
   true center (confirmed via real getBoundingClientRect measurements: the
   dots row below centered correctly at the page's true center, the
   mode-tag's box did not). A 3-column grid with an equal phantom spacer on
   BOTH sides fixes this for real: the middle column is symmetric around the
   true center regardless of what sits in the side columns. */
.question-top-row {
  display: grid;
  grid-template-columns: 90px 1fr 90px;
  align-items: start;
  gap: 10px;
  width: 100%;
}

.question-top-row .mode-tag {
  grid-column: 2;
}

.mascot-corner {
  grid-column: 3;
  justify-self: end;
  font-size: 36px;
}

.progress-dots {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
}

.progress-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--color-border);
}

.progress-dot-done {
  background: var(--color-success);
}

.progress-dot-current {
  background: var(--color-primary);
}

/* .screen's align-items:center means flex children shrink-to-fit by default
   instead of stretching -- .prompt needs a real width to size against
   (the grid layout was rendering cells tall and narrow because its width:100%
   was resolving against a shrunk-to-content parent, not the actual screen
   width), so give it one explicitly. */
.prompt {
  width: 100%;
}

/* Brief cross-fade when a stepped question (e.g. column-addition) advances
   from one sub-step to the next -- signals "same problem, next part" rather
   than the instant hard cut of a full screen replace. */
.prompt.step-fade,
.options-grid.step-fade {
  opacity: 0;
  transition: opacity 0.15s ease;
}

.mascot {
  font-size: 64px;
  line-height: 1;
  display: inline-block;
  animation: mascot-idle-bob 3s ease-in-out infinite;
}

/* Scout the fox's idle "alive" state -- a slow, subtle bob+tilt (not a
   static pasted emoji) on every screen he appears on. Reaction moods below
   are more specific selectors, so they override this while active and it
   resumes automatically once the mood class is removed. */
@keyframes mascot-idle-bob {
  0%,
  100% {
    transform: translateY(0) rotate(0deg);
  }
  50% {
    transform: translateY(-4px) rotate(-3deg);
  }
}

/* A correct teach-step check -- quick, happy double-bounce. Never shown for
   a wrong answer (see .mascot-encourage) -- a mascot that looks disappointed
   at a wrong answer actively discourages a young kid, the opposite of what
   he's there for. */
@keyframes mascot-happy {
  0% {
    transform: scale(1) rotate(0deg);
  }
  30% {
    transform: scale(1.3) rotate(-8deg);
  }
  60% {
    transform: scale(1.15) rotate(6deg);
  }
  100% {
    transform: scale(1) rotate(0deg);
  }
}
.mascot.mascot-happy {
  animation: mascot-happy 0.6s ease-in-out;
}

/* A wrong teach-step check -- a gentle, warm head-tilt wobble. Deliberately
   NOT a sad/disappointed animation (no droop, no frown) -- "nice try, let's
   figure this out together," matching the app's existing non-punitive
   feedback copy ("Not quite -- try another one!"). */
@keyframes mascot-encourage {
  0%,
  100% {
    transform: rotate(0deg);
  }
  25% {
    transform: rotate(-6deg);
  }
  75% {
    transform: rotate(6deg);
  }
}
.mascot.mascot-encourage {
  animation: mascot-encourage 0.5s ease-in-out;
}

h1 {
  font-size: 28px;
  margin: 0;
}

/* The "what's next for me" recommendation card -- sits above the subject
   grid, distinct from it (a filled, highlighted card vs. the grid's plain
   tiles) so it reads as a suggestion worth noticing, not just a 5th tile. */
.recommend-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 18px;
  border-radius: 20px;
  background: var(--color-primary);
  color: white;
  box-shadow: 0 4px 0 rgba(0, 0, 0, var(--shadow-opacity));
}

.recommend-eyebrow {
  margin: 0;
  font-size: 14px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  opacity: 0.85;
}

.recommend-body {
  display: flex;
  align-items: center;
  gap: 12px;
}

.recommend-text {
  text-align: left;
}

.recommend-name {
  margin: 0;
  font-size: 20px;
  font-weight: bold;
}

.recommend-subject {
  margin: 0;
  font-size: 14px;
  opacity: 0.85;
}

/* Compound selector, not just .recommend-start-btn -- the element also
   carries .big-btn (see ui.js's recommendCardHtml), and .big-btn's own
   background rule appears LATER in this file with equal specificity, so it
   silently won and made this button invisible (same pink as the card behind
   it) -- a real bug a live audit caught via computed-style comparison, not
   a hypothetical one. */
.big-btn.recommend-start-btn {
  background: white;
  color: var(--color-primary);
  min-width: 160px;
}

.subject-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 16px;
  width: 100%;
}

@media (min-width: 480px) {
  .subject-grid {
    /* 2x2, not 3+1 -- 4 tiles in a 3-column grid would strand the 4th alone
       on its own row, same issue fixed for the 5-option question layout. */
    grid-template-columns: repeat(2, 1fr);
  }

  /* 5 subjects (Word Play added) -- same 3-over-2 pyramid already solved for
     the 5-option question layout below, reused here for the same reason: a
     lone 5th tile on its own row in a 2-column grid reads as a leftover. */
  .subject-grid.count-5 {
    grid-template-columns: repeat(6, 1fr);
  }
  .subject-grid.count-5 .subject-btn:nth-child(1) {
    grid-column: 1 / 3;
  }
  .subject-grid.count-5 .subject-btn:nth-child(2) {
    grid-column: 3 / 5;
  }
  .subject-grid.count-5 .subject-btn:nth-child(3) {
    grid-column: 5 / 7;
  }
  .subject-grid.count-5 .subject-btn:nth-child(4) {
    grid-column: 2 / 4;
  }
  .subject-grid.count-5 .subject-btn:nth-child(5) {
    grid-column: 4 / 6;
  }
}

.subject-btn,
.big-btn,
.option-btn,
.hint-btn,
.level-row,
.keypad-btn,
.spell-tile {
  min-height: 88px;
  min-width: 88px;
  font-size: 24px;
  border-radius: 20px;
  border: 2px solid var(--color-border);
  cursor: pointer;
  padding: 16px;
  background: var(--color-secondary);
  color: var(--color-text);
  box-shadow: 0 4px 0 rgba(0, 0, 0, var(--shadow-opacity));
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}

.subject-btn:active,
.big-btn:active,
.option-btn:active,
.level-row:active,
.keypad-btn:active,
.spell-tile:active {
  transform: translateY(2px) scale(0.98);
  box-shadow: 0 2px 0 rgba(0, 0, 0, var(--shadow-opacity));
}

.subject-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.subject-btn span {
  font-size: 18px;
}

.subject-progress {
  display: block;
  width: 100%;
  max-width: 120px;
  height: 8px;
  border-radius: 4px;
  background: var(--color-info-bg);
  overflow: hidden;
}

.subject-progress-fill {
  display: block;
  height: 100%;
  background: var(--color-info);
  border-radius: 4px;
}

.subject-status {
  font-size: 14px;
  color: var(--color-text-muted);
}

.home-topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}

.home-greeting {
  display: flex;
  align-items: center;
  gap: 12px;
  text-align: left;
}

.home-greeting-text h1 {
  font-size: 28px;
  margin: 0;
}

.home-subtitle {
  margin: 2px 0 0;
  font-size: 15px;
  color: var(--color-text-muted);
  font-family: 'Andika', 'Comic Sans MS', ui-rounded, system-ui, sans-serif;
}

.home-topbar-links {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  flex: none;
}

.level-picker {
  align-items: stretch;
}

.level-list {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
}

/* Groups levels into a collapsible accordion -- by named strand (e.g.
   "Time", "Money") when the subject's concepts carry one, otherwise by
   major level number ("1.1" -> "Level 1"). Only the up-next group starts
   open; a flat 15+ row ladder (Pattern Puzzles has 18) reads as one
   overwhelming wall otherwise. See renderLevelPicker in ui.js. */
.level-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.level-group-header {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 14px 18px;
  border-radius: 16px;
  border: 2px solid var(--color-border);
  background: var(--color-secondary);
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
  font-family: inherit;
  font-size: 17px;
  font-weight: bold;
  box-shadow: 0 4px 0 rgba(0, 0, 0, var(--shadow-opacity));
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}

.level-group-header:active {
  transform: translateY(2px) scale(0.98);
  box-shadow: 0 2px 0 rgba(0, 0, 0, var(--shadow-opacity));
}

.level-group-open .level-group-header {
  border-color: var(--color-primary);
}

.level-group-title {
  flex: 1;
  min-width: 0;
}

.level-group-meta {
  font-size: 13px;
  font-weight: normal;
  color: var(--color-text-muted);
  flex: none;
}

.level-group-chevron {
  flex: none;
  font-size: 15px;
  color: var(--color-text-muted);
}

.level-group-items {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-left: 20px;
  padding-left: 14px;
  border-left: 3px solid var(--color-border);
}

.level-group-items:empty {
  display: none;
}

.level-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  border-radius: 16px;
  border: 2px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  text-align: left;
  cursor: pointer;
  font-family: inherit;
}

.level-row-text {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}

.level-row-name {
  font-size: 18px;
  font-weight: bold;
}

.level-row-status {
  font-size: 14px;
  color: var(--color-text-muted);
}

.level-icon {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
}

.level-icon-mastered {
  background: var(--color-success);
}

.level-icon-progress {
  border: 4px solid var(--color-info);
  color: var(--color-info);
  font-family: 'Baloo 2', sans-serif;
  font-weight: 800;
  font-size: 13px;
}

.level-icon-new {
  border: 3px dashed var(--color-border);
}

.level-up-next-tag {
  flex: none;
  font-size: 13px;
  font-weight: bold;
  color: white;
  background: var(--color-primary);
  border-radius: 999px;
  padding: 4px 12px;
}

.level-row-recommended {
  border-color: var(--color-primary);
}

.level-row-mastered .level-row-status {
  color: var(--color-success-text);
  font-weight: bold;
}

/* "Coming soon" placeholder rows (see math.js's structural placeholders,
   docs/curriculum-rules.md): honestly unbuilt, not withheld -- no lock icon,
   just a plain dimmed label. ui.js never attaches a click listener to one,
   so the shared .level-row:active press effect above is cancelled here too
   -- otherwise tapping one would visually look like it did something. */
.level-row-coming-soon {
  opacity: 0.55;
  cursor: default;
}

.level-row-coming-soon:active {
  transform: none;
  box-shadow: 0 4px 0 rgba(0, 0, 0, var(--shadow-opacity));
}

.big-btn {
  background: var(--color-primary);
  color: white;
  font-weight: bold;
  border: none;
  box-shadow: 0 6px 0 var(--color-primary-shadow);
}
.big-btn:active {
  box-shadow: 0 2px 0 var(--color-primary-shadow);
}

.big-btn.secondary {
  background: var(--color-surface);
  color: var(--color-text);
  border: 2px solid var(--color-border);
  box-shadow: 0 4px 0 var(--color-border);
}
.big-btn.secondary:active {
  box-shadow: 0 2px 0 var(--color-border);
}

.options-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
  width: 100%;
}

/* NNAT questions have 5 options (matching the real test format -- see the
   brief). On the default 2-column grid that leaves a lone 5th button by
   itself on its own row -- even centered, a single stranded button still
   reads as a leftover, not a deliberate layout. A 6-column grid with the
   first 3 buttons spanning 2 columns each (a full top row) and the last 2
   spanning 2 columns each starting one column in (a centered second row)
   gives a 3-over-2 arrangement instead, where both rows look intentional. */
.options-grid.count-5 {
  grid-template-columns: repeat(6, 1fr);
}
.options-grid.count-5 .option-btn:nth-child(1) {
  grid-column: 1 / 3;
}
.options-grid.count-5 .option-btn:nth-child(2) {
  grid-column: 3 / 5;
}
.options-grid.count-5 .option-btn:nth-child(3) {
  grid-column: 5 / 7;
}
.options-grid.count-5 .option-btn:nth-child(4) {
  grid-column: 2 / 4;
}
.options-grid.count-5 .option-btn:nth-child(5) {
  grid-column: 4 / 6;
}

.option-btn {
  background: var(--color-accent);
  font-size: 42px;
  /* Text options were centered by text-align alone (an inline img would
     have centered the same way), but .coin's own display:block -- needed
     for its standalone coin-slot use -- opts it out of that, so a coin used
     as an option image (Same Value, Match the Clock) needs real flex
     centering here instead. */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  overflow-wrap: break-word;
  line-height: 1.15;
}

/* Long text options (a full place name like "Tallahassee", a multi-word
   label like "AM (morning)") overlapped/clipped at the shared 42px size --
   a real bug three separate live UX audits caught independently (capital
   names, AM/PM labels, NNAT Pattern Grid options), not a hypothetical one.
   ui.js measures each option's own text length (optionSizeClass) and adds
   one of these so longer text gets real room instead of wrapping over
   itself. */
.option-btn-sm {
  font-size: 26px;
}

.option-btn-xs {
  font-size: 19px;
}

.option-btn.correct {
  background: var(--color-success-bg);
  color: var(--color-success-text);
  border: 3px solid var(--color-success);
  animation: correct-bounce 0.4s ease;
}

.option-btn.retry {
  background: var(--color-retry);
  border: 3px dashed var(--color-retry-border);
  color: var(--color-text-muted);
}

/* A wrong pick still leaves the RIGHT option marked -- a ring, not full
   "correct" green (that would misleadingly suggest the kid picked it) --
   so a confident-but-wrong guess ends with a real answer instead of just
   silently moving to a fresh question. A real audit-caught gap, app-wide. */
.option-btn.correct-reveal {
  box-shadow: 0 0 0 4px var(--color-success), 0 4px 0 rgba(0, 0, 0, var(--shadow-opacity));
}

/* Typed-answer entry (a number pad) -- an alternative to the options grid
   for skills that are genuinely production tasks (see the brief: TEKS 1.4(B)
   asks a kid to WRITE a value, not pick it out of a lineup). */
.numeric-entry {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  width: 100%;
  max-width: 340px;
  margin: 0 auto;
}

.numeric-display {
  min-height: 64px;
  min-width: 140px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: 40px;
  font-weight: bold;
  border-bottom: 4px solid var(--color-text);
  padding: 0 12px;
}

.numeric-unit {
  color: var(--color-primary);
}

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  width: 100%;
}

.keypad-btn {
  background: var(--color-accent);
  font-size: 32px;
}

.keypad-clear {
  font-size: 18px;
}

.keypad-check {
  background: var(--color-primary);
  color: white;
  font-size: 18px;
  font-weight: bold;
}

/* Spell-the-word (Word Play): a picture, one blank per letter, and a tile
   bank of the word's own letters (shuffled) below -- see renderSpellQuestion
   in ui.js for why this is an ordering task, not a full keyboard. */
.spell-image {
  font-size: 96px;
  line-height: 1;
  margin-bottom: 8px;
}

.spell-slots {
  display: flex;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}

.spell-slot {
  min-width: 44px;
  min-height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32px;
  font-weight: bold;
  border-bottom: 4px solid var(--color-text);
  text-transform: uppercase;
  color: var(--color-text);
  opacity: 0.35;
}

.spell-slot-filled {
  opacity: 1;
  border-bottom-color: var(--color-primary);
}

.spell-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
}

.spell-tile {
  background: var(--color-accent);
  font-size: 28px;
  font-weight: bold;
  text-transform: uppercase;
}

.spell-tile:disabled {
  opacity: 0.3;
  cursor: default;
}

.spell-undo {
  background: var(--color-secondary);
  font-size: 22px;
}

.keypad.correct .keypad-check {
  background: var(--color-success);
  animation: correct-bounce 0.4s ease;
}

.keypad.retry .keypad-check {
  background: var(--color-retry);
}

@keyframes correct-bounce {
  0% {
    transform: scale(1);
  }
  40% {
    transform: scale(1.08);
  }
  100% {
    transform: scale(1);
  }
}

.confetti-particle {
  position: fixed;
  font-size: 20px;
  pointer-events: none;
  z-index: 1000;
  animation: confetti-burst 0.7s ease-out forwards;
}

@keyframes confetti-burst {
  0% {
    transform: translate(0, 0) scale(1);
    opacity: 1;
  }
  100% {
    transform: translate(var(--dx), var(--dy)) scale(0.5);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .option-btn.correct {
    animation: none;
  }
  .confetti-particle {
    display: none;
  }
}

.sequence,
.objects {
  display: flex;
  gap: 12px;
  justify-content: center;
  font-size: 48px;
  flex-wrap: wrap;
}

.seq-item.blank {
  opacity: 0.5;
}

/* Reserves real space at the bottom of the page whenever the current screen
   overflows -- so the "more below" cue below always sits in genuine empty
   space, never floating over a live button. The first version just fixed
   the cue to the viewport bottom with no reserved room; live audits at
   tablet widths (768x1024, 1024x768) caught it landing directly on top of a
   Clear button, a Level-row's status text, and a pair of answer buttons,
   because at those widths the overflow is often only tens of pixels --
   nowhere near enough clearance for a floating pill. Toggled once per
   screen render (see attachScrollCue in ui.js), independent of scroll
   position, so this doesn't reflow while the kid is mid-scroll. */
body.scroll-cue-active {
  padding-bottom: calc(max(16px, env(safe-area-inset-bottom)) + 56px);
}

.scroll-cue {
  position: fixed;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  background: var(--color-text);
  color: var(--color-bg);
  font-size: 13px;
  font-weight: bold;
  padding: 6px 14px;
  border-radius: 999px;
  opacity: 0.85;
  pointer-events: none;
  z-index: 500;
  animation: scroll-cue-bounce 1.1s ease-in-out infinite;
}

@keyframes scroll-cue-bounce {
  0%,
  100% {
    transform: translate(-50%, 0);
  }
  50% {
    transform: translate(-50%, 4px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue {
    animation: none;
  }
}

.feedback {
  min-height: 28px;
  font-size: 20px;
  font-weight: bold;
}

.feedback.correct,
.feedback.retry {
  border-radius: 18px;
  padding: 14px 18px;
}

.feedback.correct {
  background: var(--color-success-bg);
  color: var(--color-feedback-correct);
}

.feedback.retry {
  background: var(--color-retry);
  color: var(--color-feedback-retry);
}

.feedback.hint {
  color: var(--color-feedback-retry);
  font-weight: normal;
}

.teach-check-feedback {
  min-height: 28px;
  font-size: 20px;
  font-weight: bold;
}

.teach-check-feedback.correct {
  color: var(--color-feedback-correct);
}

.teach-check-feedback.retry {
  color: var(--color-feedback-retry);
}

.hint-btn {
  background: transparent;
  border: 2px dashed var(--color-border);
  min-height: 56px;
  font-size: 18px;
}

/* "Every section should start with a short tutorial (that you can skip)" --
   same visual recipe as .hint-btn (transparent, dashed, secondary) since
   both are the same kind of thing: an optional, clearly-secondary action
   next to the primary Next/Let's-go button, never competing with it. */
.skip-tutorial-btn {
  background: transparent;
  border: 2px dashed var(--color-border);
  min-height: 56px;
  font-size: 16px;
  opacity: 0.8;
}

.mode-tag {
  font-size: 16px;
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.teach-text,
.teach-question,
.teach-reveal,
.fact-prompt,
.equation-prompt {
  font-size: 22px;
  max-width: 90%;
}

.teach-hook {
  font-size: 18px;
  font-style: italic;
  opacity: 0.85;
}

.skip-confirm-text {
  font-size: 20px;
  max-width: 90%;
}

/* Auth gate / profile picker -- the first real text inputs in the app
   (everything else uses tap-only widgets: a number pad, letter tiles).
   Parent-facing only (reached from Parent Mode, never shown to the kid),
   so this can afford to look more like a normal form field than the big
   playful kid-facing buttons elsewhere. */
.auth-email-input,
.profile-name-input {
  width: 100%;
  max-width: 340px;
  min-height: 56px;
  font-size: 18px;
  font-family: inherit;
  padding: 12px 16px;
  border: 3px solid var(--color-accent);
  border-radius: 14px;
  background: white;
  color: var(--color-text);
}

.auth-email-input:focus,
.profile-name-input:focus {
  outline: none;
  border-color: var(--color-primary);
}

.auth-status {
  font-size: 15px;
  opacity: 0.8;
}

.auth-status-ok {
  color: var(--color-feedback-correct);
  font-weight: bold;
  opacity: 1;
}

/* Post-mastery bonus trick reveal -- deliberately distinct from a teach
   screen (a badge, a title, no question beat) so it reads as a fun reward
   moment rather than another lesson. See the brief's comparative-pedagogy
   pass on why tricks are gated to AFTER mastery, never before. */
.trick-badge {
  display: inline-block;
  background: var(--color-accent);
  color: var(--color-text);
  font-weight: bold;
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 8px 16px;
  border-radius: 999px;
}

.trick-title {
  font-size: 30px;
  color: var(--color-primary);
  margin: 4px 0;
}

.trick-text {
  font-size: 22px;
  max-width: 90%;
}

/* "Did You Know?" fun fact, shown once at session start -- see the brief on
   why (real-world/historical context, not just drilling). Same badge/no-
   question-beat treatment as the trick reveal, but its own accent color so
   the two don't read as the same kind of moment. */
.fun-fact-badge {
  display: inline-block;
  background: var(--color-info);
  color: white;
  font-weight: bold;
  font-size: 16px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 8px 16px;
  border-radius: 999px;
}

.fun-fact-text {
  font-size: 20px;
  max-width: 92%;
  line-height: 1.4;
}

/* The "four coins, four presidents" fun fact -- real coin photos (same
   .coin-slot used on every coin-ID question) next to a short real bio, not
   just names packed into parentheses inside one sentence. A horizontal row
   per president (coin on the left, text filling the rest of the width)
   instead of a cramped 2x2 grid -- this app's own UX audits this session
   found cramped grids are exactly where long text clips or overlaps, and a
   president's real name + years + reason needs real room. */
.fact-coin-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 440px;
  margin: 0 auto;
}

.fact-coin-row {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--color-accent);
  border-radius: 16px;
  padding: 10px 14px;
  text-align: left;
}

.fact-coin-info {
  flex: 1;
  min-width: 0;
}

.fact-coin-name {
  font-weight: bold;
  font-size: 16px;
  margin: 0;
}

.fact-coin-years {
  font-size: 12px;
  font-weight: bold;
  opacity: 0.65;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin: 1px 0 0;
}

.fact-coin-why {
  font-size: 13px;
  line-height: 1.3;
  margin: 4px 0 0;
}

.visual-row {
  display: flex;
  gap: 12px;
  font-size: 48px;
}

.mastered {
  font-weight: bold;
  color: var(--color-success);
}

.equation-prompt {
  font-size: 40px !important;
  font-weight: bold;
}

.addition-row {
  display: flex;
  align-items: center;
  gap: 16px;
}

.op-symbol {
  font-size: 36px;
  font-weight: bold;
}

/* Vertical column addition/subtraction -- the real written procedure (see
   the brief's note on why this matters more than a bare equation), with an
   operator column, a rule line, and a carry/borrow slot above the tens
   column that can sit empty (inviting the habit) or show a revealed value. */
.colmath {
  display: grid;
  grid-template-columns: 36px 64px 64px;
  justify-items: center;
  align-items: center;
  font-size: 40px;
  font-weight: bold;
  row-gap: 2px;
  margin: 0 auto;
}

.colmath-row {
  display: contents;
}

.colmath-op {
  font-size: 32px;
  justify-self: end;
  padding-right: 4px;
}

.colmath-cell {
  min-width: 64px;
  text-align: center;
}

.colmath-blank {
  color: var(--color-text);
  opacity: 0.4;
}

.colmath-highlight {
  background: var(--color-info-bg);
  border-radius: 10px;
}

.colmath-carry-row {
  min-height: 30px;
}

.colmath-carry-cell {
  min-width: 64px;
  min-height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.colmath-carry-value {
  font-size: 22px;
  color: var(--color-primary);
}

.colmath-carry-slot {
  width: 24px;
  height: 24px;
  border: 2px dashed var(--color-accent);
  border-radius: 6px;
}

.colmath-regroup {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
  gap: 2px;
}

.colmath-old {
  font-size: 20px;
  color: var(--color-text);
  opacity: 0.55;
  text-decoration: line-through;
}

.colmath-new {
  font-size: 34px;
  color: var(--color-primary);
}

.colmath-line {
  grid-column: 1 / 4;
  border-top: 4px solid var(--color-text);
  width: 100%;
  margin: 4px 0;
}

/* Singapore-style bar model for word problems (see the brief's comparative-
   pedagogy pass) -- known parts as filled proportional bars, the unknown
   piece as a dashed "?" segment, and a bracket under the whole bar labeled
   with either "?" (whole unknown) or the real total (a part is unknown). */
.barmodel-text {
  font-size: 20px;
  margin-bottom: 14px;
}

.barmodel {
  max-width: 420px;
  margin: 0 auto;
}

.barmodel-bar {
  display: flex;
  gap: 4px;
  height: 64px;
}

.barmodel-segment {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  background: var(--color-info);
  color: white;
  border-radius: 10px;
  font-size: 28px;
  font-weight: bold;
}

.barmodel-unknown {
  background: transparent;
  border: 3px dashed var(--color-accent);
  color: var(--color-accent);
}

.barmodel-bracket {
  position: relative;
  margin-top: 8px;
  padding-top: 6px;
  border-top: 3px solid var(--color-text);
  text-align: center;
}

.barmodel-bracket::before,
.barmodel-bracket::after {
  content: '';
  position: absolute;
  top: -3px;
  width: 3px;
  height: 9px;
  background: var(--color-text);
}

.barmodel-bracket::before {
  left: 0;
}

.barmodel-bracket::after {
  right: 0;
}

.barmodel-bracket-label {
  font-size: 22px;
  font-weight: bold;
}

/* Comparison bar model (genComparisonWordProblem) -- two bars sharing the
   same left edge and the same grid column count, so a given value occupies
   identical width in both rows; the shorter bar's row gets an extra dashed
   segment showing the gap up to the longer bar's length. */
.comparebar {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 420px;
  margin: 0 auto;
}

.comparebar-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.comparebar-name {
  font-weight: bold;
  font-size: 16px;
  width: 54px;
  flex-shrink: 0;
  text-align: right;
}

.comparebar-track {
  display: grid;
  flex: 1;
  height: 36px;
  gap: 2px;
}

.comparebar-fill {
  background: var(--color-info);
  border-radius: 8px;
}

.comparebar-diff {
  background: transparent;
  border: 3px dashed var(--color-accent);
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  color: var(--color-accent);
  font-size: 16px;
}

/* Coins (TEKS 1.4) -- real photographs (see ui.js's coinImg()), not a
   drawn approximation, matching how actual coin-ID materials work. Real
   relative sizes matter here: the dime is deliberately the SMALLEST despite
   being worth more than a penny or nickel -- that mismatch is the actual
   thing this standard exists to teach. */
.coins-text {
  font-size: 20px;
  margin-bottom: 14px;
}

.coins-row {
  display: flex;
  gap: 14px;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  min-height: 84px;
}

/* Two coin groups side by side, for "which is worth more" comparison
   questions (genCoinCompare) -- a distinct schema from summing one group. */
.coins-pair {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
}

.coins-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.coins-group-label {
  font-size: 16px;
  font-weight: bold;
  opacity: 0.7;
  margin: 0;
}

.coins-vs {
  font-size: 22px;
  font-weight: bold;
  opacity: 0.5;
}

/* A fixed-size frame, sized to the biggest coin (the Quarter), shown around
   EVERY coin -- including when only one coin is on screen. A single coin
   shown with nothing beside it has no way to judge "small" or "big" from;
   this dashed ring is the constant size reference that makes the size
   comparison the content is actually testing possible at all. */
.coin-slot {
  width: 80px;
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 2px dashed rgba(20, 20, 19, 0.18);
  flex-shrink: 0;
}

.coin {
  display: block;
  filter: drop-shadow(0 2px 1px rgba(0, 0, 0, 0.2));
}

.coin-penny {
  width: 50px;
  height: 50px;
}

.coin-nickel {
  width: 58px;
  height: 58px;
}

.coin-dime {
  width: 44px;
  height: 44px;
}

.coin-quarter {
  width: 66px;
  height: 66px;
}

/* Analog clock (TEKS 1.7) -- see clockHtml() in ui.js for why the hour hand's
   angle includes the minute's fractional contribution. */
.clock-text {
  font-size: 20px;
  margin-bottom: 10px;
}

.clock-face {
  display: block;
  margin: 0 auto;
}

.clock-dial {
  fill: white;
  stroke: var(--color-text);
  stroke-width: 4;
}

.clock-number {
  font-size: 14px;
  font-weight: bold;
  fill: var(--color-text);
  text-anchor: middle;
}

.clock-hand {
  stroke: var(--color-text);
  stroke-linecap: round;
}

.clock-hour-hand {
  stroke-width: 6;
}

.clock-minute-hand {
  stroke-width: 4;
}

.clock-center {
  fill: var(--color-primary);
}

.clock-pair {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.clock-arrow {
  font-size: 32px;
  font-weight: bold;
}

/* A rich (clock-face) option button needs more breathing room than a plain
   text/number button -- override the shared min-height for this case only. */
.option-btn:has(.clock-face) {
  min-height: 110px;
  padding: 8px;
}

/* 2D shape identification & halves/fourths (Shapes strand). */
.shape-display,
.partition-display {
  display: block;
  margin: 0 auto;
}

.shape-shape {
  fill: var(--color-primary);
  stroke: var(--color-text);
  stroke-width: 3;
}

.partition-filled {
  fill: var(--color-primary);
  stroke: var(--color-text);
  stroke-width: 3;
}

.partition-empty {
  fill: white;
  stroke: var(--color-text);
  stroke-width: 3;
}

.shape-emoji {
  font-size: 96px;
  line-height: 1;
  margin-bottom: 8px;
}

.option-btn:has(.shape-display),
.option-btn:has(.partition-display) {
  min-height: 100px;
  padding: 8px;
}

.partition-pair {
  display: flex;
  gap: 24px;
  justify-content: center;
  align-items: flex-start;
}

.partition-pair-label {
  font-weight: bold;
  margin-top: 4px;
}

/* Open number line (Grade 2 Number Sense). */
.numline-track {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 0;
  padding: 30px 8px 4px;
  margin-bottom: 8px;
  border-top: 3px solid var(--color-text);
  position: relative;
}

.numline-tick {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
  min-width: 18px;
}

.numline-mark {
  width: 2px;
  height: 10px;
  background: var(--color-text);
  margin-top: -13px;
}

.numline-label {
  font-size: 11px;
  font-weight: bold;
  margin-top: 2px;
}

.numline-tick-marked .numline-mark {
  background: var(--color-primary);
  width: 3px;
}

.numline-dot {
  position: absolute;
  top: -34px;
  font-size: 28px;
  color: var(--color-primary);
}

.numline-dot::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 26px;
  width: 3px;
  height: 20px;
  background: var(--color-primary);
  transform: translateX(-50%);
}

/* US tile-grid map (State Capitals). */
.usmap-grid {
  display: grid;
  grid-template-rows: repeat(8, 1fr);
  grid-template-columns: repeat(12, 1fr);
  gap: 2px;
  width: 100%;
  max-width: 480px;
  aspect-ratio: 12 / 8;
  margin: 0 auto 12px;
}

.usmap-cell {
  background: var(--color-accent);
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 8px;
  font-weight: bold;
  color: var(--color-text);
}

.usmap-cell-highlight {
  background: var(--color-primary);
  color: white;
  font-size: 10px;
  animation: usmap-pulse 1s ease-in-out infinite alternate;
}

@keyframes usmap-pulse {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(1.12);
  }
}

.usmap-cell-marker {
  background: var(--color-info);
  color: white;
  font-size: 13px;
}

@media (prefers-reduced-motion: reduce) {
  .usmap-cell-highlight {
    animation: none;
  }
}

/* Measuring Length: a labeled object bar directly above a grid of numbered
   unit cells, BOTH full-width children of the same wrapper -- so they
   always span the identical total width, which is what actually shows
   "these units together are exactly as long as the object" (see ui.js's
   renderPrompt). The earlier version just showed the object, then a
   separate row of unit emoji below it with no visual link between them --
   a real content-clarity bug (an adult reviewer couldn't tell what the
   exercise was even asking). Numbered cells read like yard markers on a
   football field -- a concrete comparison, not an abstract pile to count. */
.length-compare {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  max-width: 420px;
  margin: 0 auto 4px;
}

.length-object-bar {
  background: var(--color-info);
  color: white;
  border-radius: 12px;
  min-height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 22px;
  font-weight: bold;
  text-transform: capitalize;
}

.length-object-bar span {
  font-size: 32px;
}

.length-units-track {
  display: grid;
  gap: 3px;
}

.length-unit-cell {
  background: var(--color-accent);
  border-radius: 8px;
  min-height: 56px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}

.length-unit-icon {
  font-size: 24px;
  line-height: 1;
}

.length-unit-number {
  font-size: 11px;
  font-weight: bold;
  opacity: 0.6;
}

/* Collecting & Reading Graphs: one labeled row per category, icons standing
   in for a real picture graph. */
.picture-graph {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 10px;
  width: 100%;
  max-width: 480px;
  margin-left: auto;
  margin-right: auto;
}

.graph-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.graph-label {
  min-width: 70px;
  font-weight: bold;
  text-align: right;
}

.graph-icons {
  display: flex;
  gap: 4px;
  font-size: 28px;
  flex-wrap: wrap;
}

/* Real vertical bar graph (TEKS 2.10) -- a bar's HEIGHT is the value,
   distinct from the pictograph above (one icon per unit). Each bar gets a
   printed value label on top since there's no axis scale drawn -- reading
   height alone off an unlabeled bar is a real ambiguity, not a cosmetic
   nicety. */
.bargraph {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 14px;
  width: 100%;
  max-width: 420px;
  margin: 0 auto 10px;
  min-height: 180px;
}

.bargraph-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  height: 100%;
  justify-content: flex-end;
}

.bargraph-value {
  font-size: 16px;
  font-weight: bold;
}

.bargraph-track {
  width: 100%;
  max-width: 48px;
  height: 140px;
  display: flex;
  align-items: flex-end;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 6px;
  overflow: hidden;
}

.bargraph-bar {
  width: 100%;
  background: var(--color-info);
  border-radius: 6px 6px 0 0;
}

.bargraph-label {
  font-size: 13px;
  font-weight: bold;
  opacity: 0.75;
}

/* Counting square units (TEKS 2.9(F)) -- a real rows x cols grid of
   identical unit cells, aspect-ratio locked to cols/rows so every cell
   renders as a true square (area IS square units -- a visibly non-square
   "unit" would undercut the exact thing this standard teaches). */
.area-grid {
  display: grid;
  gap: 3px;
  width: 100%;
  max-width: 280px;
  margin: 0 auto;
  background: var(--color-text);
  padding: 3px;
  border-radius: 8px;
}

.area-cell {
  background: var(--color-info);
  border-radius: 2px;
}

/* Equal-groups multiplication context (Grade 2). */
.groups-row {
  display: flex;
  gap: 16px;
  justify-content: center;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.group-box {
  border: 3px solid var(--color-text);
  border-radius: 12px;
  padding: 10px;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  max-width: 150px;
  justify-content: center;
  font-size: 32px;
}

/* Verbal word analogies (Word Play). */
.analogy-row {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 10px;
}

.analogy-term {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 64px;
}

.analogy-emoji {
  font-size: 40px;
  line-height: 1;
}

.analogy-word {
  font-size: 13px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.analogy-sep {
  font-size: 26px;
  font-weight: bold;
}

.analogy-sep-double {
  margin: 0 2px;
}

.analogy-term-blank .analogy-emoji,
.analogy-term-blank .analogy-word {
  opacity: 0.4;
}

.grid {
  display: grid;
  gap: 8px;
  width: 100%;
  max-width: 320px;
}

.grid-2 {
  grid-template-columns: repeat(2, 1fr);
}

.grid-3 {
  grid-template-columns: repeat(3, 1fr);
}

.grid-4 {
  grid-template-columns: repeat(4, 1fr);
  max-width: 360px;
}

/* Reasoning-by-analogy layout: same 2x2 grid mechanics, but the top row is
   the given example and the bottom row is what the child completes -- a
   bigger gap between rows (row-gap) than between columns (column-gap) makes
   that pairing visible instead of reading as four equal boxes. */
.grid-analogy {
  row-gap: 24px;
  column-gap: 8px;
}

.grid-cell {
  background: var(--color-accent);
  border-radius: 12px;
  min-height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  flex-wrap: wrap;
  font-size: 22px;
}

.grid-cell.blank {
  opacity: 0.6;
  font-size: 32px;
  font-weight: bold;
}

/* "Show a grown-up" card (Claude Design redesign) -- a real, concrete
   question for the parent to ask, not just a bare "show this screen!"
   line. Uses the same peach accent as the mastery-trick reveal/mascot
   badge circles elsewhere, so it reads as its own distinct moment. */
.grownup-card {
  width: 100%;
  background: var(--color-peach);
  border-radius: 20px;
  padding: 16px 20px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  text-align: left;
}

.grownup-eyebrow {
  font-size: 13px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-peach-text);
}

.grownup-question {
  margin: 0;
  font-size: 18px;
  font-weight: bold;
  color: var(--color-text);
}

.concept-label {
  font-size: 18px;
  opacity: 0.75;
  margin: -12px 0 0;
}

.score-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: var(--color-accent);
  border-radius: 20px;
  padding: 16px 24px;
  width: 100%;
}

.rank-name {
  font-size: 28px;
  font-weight: bold;
  margin: 0;
}

.score-total {
  font-size: 22px;
  font-weight: bold;
  margin: 0;
}

.score-line {
  font-size: 16px;
  margin: 0;
}

.rank-progress {
  font-size: 14px;
  opacity: 0.8;
  margin: 6px 0 0;
}

/* Session-end "you just did awesome" celebration -- direct feedback: "how
   can we make it feel special... like when you are playing sonic or mario
   and they want a kid to feel really great about coin bonuses." A bigger
   mascot entrance, a staggered reveal of each score line (so a streak or
   speed bonus visibly "arrives" rather than just sitting there as flat
   text already fully rendered), and an arcade-style count-up on the total
   (see ui.js's countUp/celebrate calls in renderSessionEnd). */
@keyframes mascot-celebrate {
  0% {
    transform: scale(0) rotate(-15deg);
    opacity: 0;
  }
  60% {
    transform: scale(1.25) rotate(8deg);
    opacity: 1;
  }
  100% {
    transform: scale(1) rotate(0deg);
    opacity: 1;
  }
}

.session-end-mascot {
  animation: mascot-celebrate 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@keyframes pop-in {
  0% {
    opacity: 0;
    transform: scale(0.7) translateY(8px);
  }
  60% {
    opacity: 1;
    transform: scale(1.05) translateY(0);
  }
  100% {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

/* `backwards` fill-mode: stays at the 0% (invisible) state during its own
   animation-delay instead of flashing fully visible then popping -- that's
   what makes the stagger (each element gets a later animation-delay, set
   inline in ui.js) actually read as "one at a time," not "all at once." */
.session-end-pop,
.score-line-pop {
  animation: pop-in 0.4s ease backwards;
}

.rank-name-pop {
  animation: pop-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.25s backwards;
}

.score-bonus-line {
  color: var(--color-primary);
  font-weight: bold;
}

.speed-clarity {
  font-size: 14px;
  opacity: 0.85;
  margin: 6px 0 0;
}

.speed-clarity-max {
  color: var(--color-feedback-correct);
  font-weight: bold;
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .session-end-mascot,
  .session-end-pop,
  .score-line-pop,
  .rank-name-pop,
  .mascot,
  .mascot.mascot-happy,
  .mascot.mascot-encourage {
    animation: none;
  }
}

/* Chomp Chase mini-game -- deliberately a different visual mode (black arcade
   background, not the app's warm palette) so it reads as "you've left
   practice mode for a fun break," not as another lesson screen. */
.chomp-screen {
  gap: 12px;
}

.chomp-hud {
  display: flex;
  justify-content: space-between;
  width: 100%;
  font-size: 18px;
  font-weight: bold;
}

.chomp-lives {
  color: #ff4d4d;
  letter-spacing: 2px;
}

.chomp-canvas {
  border-radius: 12px;
  background: #000;
  touch-action: none;
}

.chomp-dpad {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.chomp-dpad-row {
  display: flex;
  gap: 88px;
}

/* Matches the app's other large touch targets (.option-btn/.big-btn are
   min-height:88px) -- was 64px, smaller than everywhere else in the app. */
.chomp-dpad-btn {
  min-width: 88px;
  min-height: 88px;
  font-size: 32px;
  border-radius: 20px;
  border: none;
  cursor: pointer;
  background: var(--color-secondary);
  color: var(--color-text);
  box-shadow: 0 4px 0 rgba(0, 0, 0, var(--shadow-opacity));
}

.chomp-dpad-btn:active {
  transform: translateY(2px);
  box-shadow: 0 2px 0 rgba(0, 0, 0, var(--shadow-opacity));
}

.chomp-overlay {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  background: var(--color-accent);
  border-radius: 20px;
  padding: 24px;
  width: 100%;
}

/* An author-stylesheet `display` declaration always beats the browser's
   built-in `[hidden] { display: none }` rule, regardless of the `hidden`
   attribute -- without this, the overlay above was permanently visible
   (just pushed below the fold on short viewports, which is why it went
   unnoticed until testing at real tablet height revealed it sitting there
   during ordinary, still-in-progress gameplay). */
.chomp-overlay[hidden] {
  display: none;
}

.chomp-overlay-title {
  font-size: 24px;
  font-weight: bold;
  margin: 0;
}

.chomp-overlay-score {
  font-size: 18px;
  margin: 0;
}

/* Mini Scrabble -- unlike Chomp Chase, stays in the app's own warm palette
   (this is a calm, turn-based word game, not an arcade break) but still
   gets its own prefixed class block, same convention. */
.word-screen {
  gap: 10px;
}

.word-hud {
  display: flex;
  justify-content: space-between;
  width: 100%;
  max-width: 480px;
  font-size: 16px;
  font-weight: bold;
}

.word-board {
  display: grid;
  grid-template-columns: repeat(11, 1fr);
  gap: 2px;
  width: 100%;
  max-width: 420px;
  aspect-ratio: 1;
  background: var(--color-text);
  padding: 2px;
  border-radius: 8px;
}

.word-cell {
  background: #fdf6e8;
  border: none;
  border-radius: 2px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: bold;
  color: var(--color-text);
  padding: 0;
  aspect-ratio: 1;
  min-height: 0;
}

.word-cell-bonus {
  font-size: 8px;
  opacity: 0.85;
}

.word-cell-dl {
  background: #bfe3f0;
}

.word-cell-tl {
  background: #6fb8d6;
  color: white;
}

.word-cell-dw {
  background: #f3c9d6;
}

.word-cell-tw {
  background: #e0678f;
  color: white;
}

.word-cell-star {
  background: var(--color-primary);
  color: white;
  font-size: 16px;
}

.word-cell-filled {
  background: var(--color-accent);
  font-size: 15px;
}

.word-cell-pending {
  background: var(--color-secondary);
  font-size: 15px;
  animation: word-tile-drop 0.15s ease;
}

@keyframes word-tile-drop {
  0% {
    transform: scale(1.3);
  }
  100% {
    transform: scale(1);
  }
}

.word-feedback {
  min-height: 24px;
  font-size: 16px;
  font-weight: bold;
  margin: 0;
}

.word-feedback-success {
  color: var(--color-success, #3caa6a);
}

.word-feedback-error {
  color: var(--color-retry, #d9534f);
}

.word-rack {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  justify-content: center;
  width: 100%;
  max-width: 420px;
}

/* min-width was 46px -- at 390px-wide phones the 7th rack tile didn't fit
   alongside the other 6 and wrapped alone onto its own row (a real audit
   finding, not cosmetic guesswork: 7*46 + 6*8 gap left no margin at real
   viewport width). 42px + the smaller rack gap above lets all 7 fit. */
.word-tile {
  position: relative;
  min-width: 42px;
  min-height: 46px;
  background: var(--color-accent);
  border: none;
  border-radius: 8px;
  font-size: 20px;
  font-weight: bold;
  color: var(--color-text);
  text-transform: uppercase;
}

.word-tile-selected {
  background: var(--color-primary);
  color: white;
  transform: translateY(-4px);
}

.word-tile-value {
  position: absolute;
  bottom: 2px;
  right: 4px;
  font-size: 9px;
  font-weight: normal;
}

.word-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

.word-history {
  width: 100%;
  max-width: 480px;
  text-align: left;
}

.word-history-item {
  font-size: 14px;
  margin: 4px 0;
}

.word-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  text-align: center;
  z-index: 20;
}

.word-overlay[hidden] {
  display: none;
}

.word-overlay-title {
  font-size: 26px;
  font-weight: bold;
  color: white;
  margin: 0;
}

.word-overlay-score {
  font-size: 18px;
  color: white;
  margin: 0;
}

.word-overlay-words {
  max-height: 240px;
  overflow-y: auto;
  background: white;
  border-radius: 8px;
  padding: 10px 16px;
  max-width: 420px;
}

/* Tablet+ (roughly matching #app's own 720px cap, so this only kicks in
   once there's real room to use). A live tablet-portrait audit caught the
   US map and Scrabble board both staying capped at their phone-era
   max-width inside the much wider column, leaving noticeable unused margin
   on both sides instead of using the extra space -- not broken, just
   visibly sized for a smaller screen. Widened, still comfortably inside
   the 720px column and still centered (existing margin:0 auto rules).
   NNAT puzzle glyphs (sequence/objects/grid-cell) get a modest size bump
   too -- a live audit found the puzzle itself rendering as the visually
   LEAST prominent thing on a pattern-puzzle screen, dwarfed by the answer
   buttons below it; this doesn't touch option-btn sizing (already tuned
   this session for text-overflow), just gives the puzzle more presence. */
@media (min-width: 700px) {
  .usmap-grid {
    max-width: 640px;
  }
  .word-board {
    max-width: 560px;
  }
  .sequence,
  .objects {
    font-size: 64px;
    gap: 18px;
  }
  .grid-cell {
    font-size: 30px;
    min-height: 84px;
  }
  /* Every level-row is a full-width pill with only left-aligned two-line
     text -- at the full 720px column that's a large, consistent dead zone
     on the right of every row (a live audit caught this across a 25-row
     list). Capping the list narrower reads as a deliberately-sized list,
     not a restretched phone column, without restructuring it into a grid. */
  .level-list {
    max-width: 560px;
    margin: 0 auto;
  }
}

/* Parent Mode -- a denser, left-aligned "admin" layout, deliberately unlike
   the big, bouncy, centered kid-facing screens: this is a parent scanning a
   report, not a 6-year-old tapping large buttons. No access gate yet
   (explicit decision -- "still in testing mode," revisit later); just a
   quiet, low-visual-weight link on the home screen, not advertised anywhere
   else. */
.parent-mode-link {
  background: none;
  border: none;
  color: var(--color-text);
  opacity: 0.35;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  padding: 8px 12px;
  margin-top: 4px;
}

.parent-mode-link:active {
  opacity: 0.6;
}

/* Overrides .screen's kid-facing defaults (centered text, vertically
   centered viewport-height box, generous gap) with a standard top-anchored,
   left-aligned dashboard layout. min-height:0 (equal specificity, later in
   the file, so it wins) also means an admin view with little data yet
   doesn't get stretched to fill the whole viewport height for no reason. */
.parent-dashboard {
  align-items: stretch;
  text-align: left;
  gap: 16px;
  min-height: 0;
}

.parent-title {
  font-size: 24px;
  margin: 0;
}

.parent-totals {
  font-size: 15px;
  opacity: 0.75;
  margin: -8px 0 0;
}

/* The weekly time-spent headline leads the page now (NN/g's dashboard
   guidance: the single most important number goes top-left, glanceable,
   not buried under a wall of other stats) -- real UX research, not a
   guess, see parent-report.js. */
.parent-week-headline {
  font-size: 20px;
  font-weight: bold;
  margin: 4px 0 0;
}

.parent-totals-muted {
  opacity: 0.55;
  margin-top: 4px;
}

/* 7-day time chart -- the weekly/per-day convention real family dashboards
   use (Google Family Link's Screen Time tab). Bars share a fixed-height
   track so a 0-minute day reads as a real, visible empty column (not just
   absent), which matters for "was yesterday a gap" at a glance. */
.parent-chart {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 6px;
  width: 100%;
  margin: 10px 0 2px;
}

.parent-chart-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.parent-chart-bar-track {
  width: 100%;
  height: 64px;
  display: flex;
  align-items: flex-end;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 6px;
  overflow: hidden;
}

.parent-chart-bar {
  width: 100%;
  background: var(--color-primary);
  border-radius: 6px 6px 0 0;
}

.parent-chart-label {
  font-size: 11px;
  opacity: 0.6;
}

.parent-chart-label-today {
  font-weight: bold;
  opacity: 1;
}

/* Progressive disclosure for strands with nothing started yet -- a native
   <details>, so it needs zero JS and degrades to "just readable" with no
   styling at all if this block somehow didn't load. */
.parent-strand-more {
  margin-top: 2px;
}

.parent-strand-more summary {
  font-size: 12px;
  opacity: 0.6;
  cursor: pointer;
  padding: 4px 0;
}

.parent-strand-more .parent-strand-list {
  margin-top: 6px;
}

.parent-item-encourage {
  font-size: 14px;
  font-weight: bold;
  color: var(--color-primary);
  margin: 2px 0 0;
}

.parent-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.parent-section-title {
  font-size: 16px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  opacity: 0.65;
  margin: 0;
}

.parent-subject-row {
  background: var(--color-accent);
  border-radius: 14px;
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.parent-subject-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-weight: bold;
}

.parent-subject-name {
  font-size: 17px;
}

.parent-subject-count {
  font-size: 14px;
  opacity: 0.75;
}

.parent-bar {
  height: 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.1);
  overflow: hidden;
}

.parent-bar-fill {
  height: 100%;
  background: var(--color-primary);
  border-radius: 999px;
}

.parent-subject-meta {
  font-size: 13px;
  opacity: 0.65;
  margin: 0;
}

.parent-strand-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
}

.parent-strand-chip {
  font-size: 12px;
  background: rgba(0, 0, 0, 0.08);
  border-radius: 999px;
  padding: 3px 10px;
}

.parent-list-item {
  background: var(--color-accent);
  border-radius: 12px;
  padding: 10px 14px;
}

.parent-item-name {
  font-size: 15px;
  font-weight: bold;
  margin: 0;
}

.parent-item-subject {
  font-weight: normal;
  opacity: 0.7;
}

.parent-item-meta {
  font-size: 13px;
  opacity: 0.7;
  margin: 4px 0 0;
}

.parent-empty {
  font-size: 14px;
  opacity: 0.7;
  font-style: italic;
}

/* Lets a parent confirm their device actually picked up the latest deploy
   -- see version.js, bumped alongside sw.js's CACHE_NAME. Deliberately the
   quietest text on the page: useful for debugging, not something a parent
   needs to notice day to day. */
.parent-build-tag {
  font-size: 11px;
  opacity: 0.4;
  margin-top: 4px;
}
