/* ==========================================================================
   Math Memory — the app (9 screens)
   Responsive: full-bleed on phones, a centred column on larger screens.
   The board scales with the column; equations stay ~20% of a card's width,
   never below the 16px floor the spec sets.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Shell
   -------------------------------------------------------------------------- */

[tabindex="-1"]:focus { outline: none; }

/* The page behind everything. This has to be painted on the ROOT, not on
   .app-shell: the shell is the element the screen shake translates, and a
   background that moves with it exposes whatever is behind — which was a flat
   slab of a different purple, plus scrollbars, because a translated
   full-height element extends the scrollable area.

   color-scheme is what stops the browser drawing light scrollbar tracks over
   a dark app; overflow-x: clip suppresses the horizontal overflow the shake
   creates WITHOUT making the root a scroll container (`hidden` would force
   overflow-y to auto and break the page's own scrolling). */
html {
  background: var(--bg-3);
  color-scheme: dark;
  overflow-x: clip;
}

.app-body {
  background: transparent;
  color: #fff;
  overscroll-behavior-y: none;
  overflow-x: clip;
}

.app-shell {
  min-height: 100vh;
  min-height: 100dvh;
  background: transparent;
  display: flex;
  justify-content: center;
  transition: background .35s ease;
  /* clip, not hidden: this contains the screen shake so it cannot extend the
     document, while never becoming a scroll container itself. */
  overflow: clip;
}

/* The parent dashboard is the one light screen. It still paints on the shell,
   because nothing on it ever shakes. */
.app-shell[data-surface="light"] { background: #f5f4f1; }

/* The sized flex item. .screen fills it, so `width:100%` inside has a
   definite box to resolve against. */
.app-main {
  width: 100%;
  max-width: 560px;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

.screen {
  position: relative;
  width: 100%;
  flex: 1 1 auto;
  padding: var(--s5) var(--s5) var(--s6);
  padding-top: max(var(--s5), env(safe-area-inset-top));
  padding-bottom: max(var(--s6), env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  animation: mmfadein .3s ease both;
}

/* The warm corner glow that used to sit at top:-80px right:-70px is gone.
   It read as a smudge behind the top bar, and the backdrop (css/backdrop.css)
   now lights the page properly instead of patching one corner. */
.screen--home { overflow: hidden; }
.screen > * { position: relative; }

/* --------------------------------------------------------------------------
   Small shared parts
   -------------------------------------------------------------------------- */

.topbar { display: flex; align-items: center; gap: var(--s3); }
.topbar__title { font: 600 22px var(--font-display); color: #fff; }
.topbar__end { margin-left: auto; }

.icon-btn {
  width: var(--tap); height: var(--tap);
  border-radius: var(--r-chip);
  background: var(--sheer-3);
  display: grid; place-items: center;
  color: #fff; font-size: 18px; line-height: 1;
  flex: none;
  transition: background .15s ease, transform .15s ease;
}
.icon-btn:hover { background: rgba(255, 255, 255, .22); }
.icon-btn:active { transform: scale(.94); }
.icon-btn--light { background: #e6e4df; color: #2a2740; }
.icon-btn--light:hover { background: #dcd9d2; }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(255, 255, 255, .13);
  border-radius: var(--r-pill);
  padding: 7px 13px 7px 10px;
  font: 800 15px var(--font-ui); color: #fff;
}
.pill__icon { font-size: 15px; line-height: 1; }
.pill__icon--star { color: var(--gold); }

.chip {
  background: rgba(0, 0, 0, .25);
  border-radius: var(--r-chip);
  padding: 8px 12px;
  font: 800 13px var(--font-ui); color: #fff;
  white-space: nowrap;
}
.chip--mono { font-family: var(--font-mono); font-weight: 500; }

.panel {
  background: rgba(255, 255, 255, .1);
  border-radius: var(--r-tile);
  padding: var(--s4);
}
.panel--row {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3);
}

.bar {
  height: 10px; border-radius: 8px;
  background: var(--well);
  overflow: hidden;
}
.bar__fill {
  height: 100%; border-radius: 8px;
  background: linear-gradient(90deg, var(--teal), var(--gold));
  transition: width .5s cubic-bezier(.2, .8, .3, 1);
}

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 9px; }
.span-2 { grid-column: span 2; }

.scroller {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: var(--s2);
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .25) transparent;
}
.scroller::-webkit-scrollbar { width: 6px; }
.scroller::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, .22); border-radius: 3px;
}

.stat {
  background: rgba(255, 255, 255, .11);
  border-radius: var(--r-tile);
  padding: 14px;
}
.stat__value { font: 800 22px var(--font-ui); color: #fff; }
.stat__label {
  font: 700 11px var(--font-ui);
  color: rgba(255, 255, 255, .6);
  letter-spacing: .04em;
  margin-top: 2px;
}

.note {
  background: rgba(0, 0, 0, .22);
  border-radius: var(--r-tile);
  padding: 14px;
  font: 700 12.5px/1.6 var(--font-ui);
  color: rgba(255, 255, 255, .75);
}

.cta {
  display: block; width: 100%;
  background: linear-gradient(180deg, var(--cta-2), var(--cta));
  border-radius: 26px;
  padding: 20px;
  text-align: center;
  font: 700 clamp(24px, 7vw, 32px)/1 var(--font-display);
  color: #5b2c00;
  letter-spacing: .5px;
  box-shadow: 0 8px 0 var(--cta-shadow), 0 16px 30px rgba(255, 160, 0, .35);
  transition: transform .12s ease, box-shadow .12s ease;
}
.cta:hover { transform: translateY(-2px); box-shadow: 0 10px 0 var(--cta-shadow), 0 18px 34px rgba(255, 160, 0, .4); }
.cta:active { transform: translateY(6px); box-shadow: 0 2px 0 var(--cta-shadow); }
.cta--pulse { animation: mmpulse 2.6s ease-out infinite; }
.cta--teal {
  background: linear-gradient(180deg, var(--teal-2), var(--teal-3));
  color: #04352f;
  box-shadow: 0 7px 0 var(--teal-deep);
  border-radius: 24px;
  font-size: clamp(20px, 6vw, 26px);
}
.cta--teal:hover { box-shadow: 0 9px 0 var(--teal-deep); }
.cta--teal:active { box-shadow: 0 2px 0 var(--teal-deep); }

.link-tile {
  background: rgba(255, 255, 255, .12);
  border-radius: var(--r-tile);
  padding: 14px;
  display: flex; align-items: center; gap: 10px;
  font: 800 14px var(--font-ui); color: #fff;
  min-height: var(--tap);
  transition: background .15s ease, transform .15s ease;
}
.link-tile:hover { background: rgba(255, 255, 255, .19); }
.link-tile:active { transform: scale(.98); }
.link-tile__icon { font-size: 22px; line-height: 1; }
.link-tile--speed {
  background: linear-gradient(90deg, rgba(52, 209, 192, .28), rgba(155, 77, 255, .28));
  border: 1.5px solid rgba(255, 255, 255, .22);
  justify-content: space-between;
}
.link-tile__best { font: 700 11px var(--font-ui); color: var(--gold); }

/* --------------------------------------------------------------------------
   Home
   -------------------------------------------------------------------------- */

.home__title {
  font: 700 clamp(30px, 9vw, 40px)/1 var(--font-display);
  color: #fff; letter-spacing: -.5px;
  text-align: center;
}
.home__tagline {
  font: 600 13px var(--font-ui);
  color: rgba(255, 255, 255, .6);
  text-align: center; margin-top: 4px;
}
.home__hero {
  display: flex; align-items: flex-end; justify-content: center; gap: 10px;
  margin-block: auto;   /* absorbs spare height on tall screens */
}
.home__level-line {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3);
}
.home__level-name { font: 800 14px var(--font-ui); color: #fff; }
.home__level-meta { font: 700 12px var(--font-ui); color: rgba(255, 255, 255, .6); }

/* --------------------------------------------------------------------------
   Labelled progress bars

   A bar on its own is an invitation to guess what it measures, and the guess
   used to be wrong: the one on Home filled with steps walked while the number
   beside it counted facts mastered. Every bar now carries its own name and
   its own figure, so two measures can sit together without either being read
   as an explanation of the other.
   -------------------------------------------------------------------------- */
.meter + .meter { margin-top: 11px; }
.meter__line {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3);
  margin-bottom: 6px;
}
.meter__name  { font: 800 12px var(--font-ui); color: rgba(255, 255, 255, .82); }
.meter__value {
  font: 700 12px var(--font-ui);
  color: rgba(255, 255, 255, .6);
  font-variant-numeric: tabular-nums;
}
.home__progress .home__level-line { margin-bottom: 11px; }
/* The second measure is the quieter one: knowing a fact cold takes longer
   than walking a step, so its bar should not read as the one falling behind. */
.meter--soft .bar__fill { background: linear-gradient(90deg, var(--green-soft), var(--teal)); }

/* --------------------------------------------------------------------------
   The Multiplication Table Map

   One section per table, four nodes each: three lessons and the test that
   opens the next table. The path winds rather than running straight down, the
   nodes are the same chunky 3D pieces as the rest of the app, and the section
   colour is the table's own — a map may colour by table because no card ever
   does, so nothing here can give a pair away.
   -------------------------------------------------------------------------- */

/* The path is long — nine tables of four nodes — so the map is the one screen
   that scrolls its own list rather than the page. That is not a stylistic
   choice: .app-shell computes to `overflow: clip` on both axes (clipping one
   axis forces the other), which makes every sticky element inside it a sticky
   element with nowhere to stick. Giving the list a real scrollport of its own
   is what makes the table headers stick — and it keeps the back button and
   the star count on screen the whole way down. */
.screen--map {
  max-height: 100vh;
  max-height: 100dvh;
  overflow: hidden;
}
.scroller--map { padding-top: 56px; padding-bottom: var(--s3); }

.unit { margin: 0 0 var(--s3); }

/* The header sticks so a child scrolling the middle of a long table can
   always see which table they are in — to the BOTTOM of the viewport, because
   on a climbing path the banner sits at the foot of the table it names. */
.unit__head {
  position: sticky;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 11px var(--s4);
  margin-top: var(--s2);
  border-radius: var(--r-tile, 20px);
  background: linear-gradient(180deg, var(--tint), var(--shade));
  box-shadow: 0 4px 0 rgba(0, 0, 0, .3), 0 8px 18px rgba(0, 0, 0, .3);
  color: var(--ink-2);
}
.unit__text { flex: 1; min-width: 0; }
.unit__label {
  font: 800 11px var(--font-ui);
  letter-spacing: .12em;
  opacity: .72;
}
.unit__name {
  font: 600 20px var(--font-display);
  margin: 1px 0 0;
  color: var(--ink-2);
}
.unit__sub {
  font: 700 12px var(--font-ui);
  opacity: .78;
  margin-top: 1px;
}
.unit__count {
  flex: none;
  font: 800 14px var(--font-mono, var(--font-ui));
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .45);
  color: var(--ink-2);
}

/* A table that has not been opened yet is still visible — seeing what is
   ahead is most of what makes a map worth having — but it is plainly out of
   reach and never looks like something that failed to load. */
.unit--locked .unit__head {
  background: rgba(255, 255, 255, .1);
  box-shadow: 0 4px 0 rgba(0, 0, 0, .22);
  color: rgba(255, 255, 255, .62);
}
.unit--locked .unit__name,
.unit--locked .unit__count { color: rgba(255, 255, 255, .62); }
.unit--locked .unit__count { background: rgba(255, 255, 255, .12); }
.unit--locked .path { opacity: .55; }

.path {
  list-style: none;
  margin: 0;
  /* The top padding is for the START bubble: it sits above its node, and the
     first node of a table would otherwise wear it under the sticky header. */
  padding: 30px 0 var(--s3);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}

.path__slot {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  transform: translateX(var(--shift, 0));
}

/* The dotted trail, drawn upwards from each node to the one above. It stops
   at the edges of a table: the banner between two tables is the join, and a
   trail running across it would read as one continuous climb rather than as
   arriving somewhere new. */
.path__slot + .path__slot::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: calc(100% + 4px);
  width: 4px;
  height: 18px;
  margin-left: -2px;
  border-radius: 2px;
  background-image: radial-gradient(circle, rgba(255, 255, 255, .34) 42%, transparent 45%);
  background-size: 4px 7px;
  background-repeat: repeat-y;
}

.node {
  position: relative;
  width: 76px;
  height: 76px;
  border: 0;
  padding: 0;
  border-radius: 50%;
  display: grid;
  place-items: center;
  cursor: pointer;
  background: linear-gradient(180deg, var(--tint), var(--shade));
  box-shadow: 0 7px 0 var(--shade), 0 11px 16px rgba(0, 0, 0, .34);
  transition: transform .12s ease, box-shadow .12s ease;
}
.node:not(:disabled):hover { transform: translateY(-2px); }
.node:not(:disabled):active {
  transform: translateY(6px);
  box-shadow: 0 1px 0 var(--shade), 0 3px 8px rgba(0, 0, 0, .3);
}

.node__face {
  font-size: 30px;
  line-height: 1;
  color: #fffdf6;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .22);
}

/* Which lesson this is, small enough to be a label rather than a score. */
.node__num {
  position: absolute;
  right: -2px;
  bottom: -2px;
  min-width: 22px;
  height: 22px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  font: 800 11px var(--font-ui);
  color: var(--ink-2);
  background: rgba(255, 255, 255, .92);
  box-shadow: 0 2px 0 rgba(0, 0, 0, .2);
}

.node__stars {
  font-size: 12px;
  letter-spacing: 2px;
  color: var(--gold);
  min-height: 15px;
}

/* The test is the biggest thing in its table, because it is the thing the
   three lessons are for. */
.node--test {
  width: 88px;
  height: 88px;
  border-radius: 30px;
}
.node--test .node__face { font-size: 36px; }

.node.is-done .node__face { color: #fffdf6; }
.node.is-done::after {
  content: '✓';
  position: absolute;
  top: -3px;
  left: -3px;
  width: 24px;
  height: 24px;
  border-radius: 999px;
  display: grid;
  place-items: center;
  font: 800 13px var(--font-ui);
  color: var(--ink-2);
  background: var(--gold);
  box-shadow: 0 2px 0 rgba(0, 0, 0, .22);
}

.node.is-locked {
  background: rgba(255, 255, 255, .1);
  box-shadow: 0 6px 0 rgba(0, 0, 0, .26);
  cursor: not-allowed;
}
.node.is-locked .node__face {
  font-size: 24px;
  opacity: .5;
  text-shadow: none;
}
.node.is-locked .node__num {
  background: rgba(255, 255, 255, .22);
  color: rgba(255, 255, 255, .6);
}

/* Where the child is. The ring pulses and the bubble bobs — the two things
   Duolingo uses to say "here", and they work because nothing else on the
   screen moves. */
.node.is-here { animation: mmpulse 2.4s ease-out infinite; }
.node.is-fresh {
  background-image: linear-gradient(180deg, var(--tint), var(--shade)),
                    linear-gradient(100deg, transparent 40%, rgba(255, 255, 255, .8) 50%, transparent 60%);
  background-size: 100% 100%, 220% 100%;
  background-blend-mode: normal, overlay;
  animation: mmshimmer 1.6s ease-out 2;
}

.node__start {
  position: absolute;
  bottom: calc(100% + 10px);
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 13px;
  border-radius: 12px;
  white-space: nowrap;
  font: 800 12px var(--font-ui);
  letter-spacing: .06em;
  color: var(--ink);
  background: #fffdf6;
  box-shadow: 0 3px 0 rgba(0, 0, 0, .26);
  /* Its own bob, rather than the shared mmbob: an animation that touches
     `transform` replaces the element's `transform` outright, so the shared
     keyframes silently dropped the translateX(-50%) that centres the bubble
     over its node. The centring has to travel inside the keyframes. */
  animation: mmbobstart 1.8s ease-in-out infinite;
  z-index: 2;
}
@keyframes mmbobstart {
  0%, 100% { transform: translate(-50%, 0); }
  50%      { transform: translate(-50%, -6px); }
}
/* Quackers gets the first word. The bubble comes back when he has finished
   speaking, rather than the two of them talking over each other in the same
   piece of screen — the same rule the quiz follows for his voice. */
.node__start.is-hushed { opacity: 0; }
.node__start { transition: opacity .25s ease; }

.node__start::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -6px;
  border: 6px solid transparent;
  border-top-color: #fffdf6;
}

/* Quackers walks the path too: he stands beside the node that is next, on
   whichever side the node is not leaning towards. */
.map__duck {
  position: absolute;
  bottom: -10px;
  left: calc(100% + 2px);
  pointer-events: none;
  z-index: 1;
}
.path__slot--lean-right .map__duck {
  left: auto;
  right: calc(100% + 2px);
}

/* --------------------------------------------------------------------------
   Game
   -------------------------------------------------------------------------- */

.game__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); }
.game__chips { display: flex; gap: var(--s2); flex-wrap: wrap; justify-content: flex-end; }

.feedback {
  display: flex; align-items: center; gap: 10px;
  background: rgba(255, 255, 255, .11);
  border-radius: 18px;
  padding: 9px 12px;
  min-height: 58px;
}
.feedback .mascot { --mascot: 44px; }
.feedback__text { font: 700 13.5px var(--font-ui); color: #fff; }

.pair-dots { margin-top: auto; display: flex; justify-content: center; gap: 6px; padding-top: var(--s3); }
.pair-dots i {
  width: 26px; height: 8px; border-radius: 5px;
  background: rgba(255, 255, 255, .22);
  transition: background .3s ease;
}
.pair-dots i.is-done { background: var(--gold); }

/* Speed mode preview countdown */
.peek {
  height: 6px; border-radius: 4px;
  background: rgba(255, 255, 255, .16);
  overflow: hidden;
}
.peek__fill {
  height: 100%;
  background: linear-gradient(90deg, var(--teal-2), var(--gold));
  transform-origin: left;
  animation: peekdrain linear forwards;
}
@keyframes peekdrain { from { transform: scaleX(1); } to { transform: scaleX(0); } }

/* --------------------------------------------------------------------------
   Level complete
   -------------------------------------------------------------------------- */

.overlay {
  position: fixed; inset: 0;
  background: radial-gradient(circle at 50% 30%, rgba(255, 215, 106, .28), transparent 60%),
              rgba(12, 6, 32, .72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: grid; place-items: center;
  padding: var(--s5);
  z-index: 50;
  overflow-y: auto;
}
.sheet {
  width: 100%; max-width: 420px;
  background: linear-gradient(180deg, var(--card-front), #ffeed4);
  border-radius: var(--r-sheet);
  padding: 26px 22px;
  text-align: center;
  box-shadow: 0 24px 50px rgba(0, 0, 0, .4);
  animation: mmpop .45s cubic-bezier(.2, 1.2, .4, 1) both;
}
.sheet__stars { font-size: 34px; letter-spacing: 4px; color: var(--amber); margin-bottom: 4px; min-height: 42px; }
.sheet__stars i { display: inline-block; opacity: 0; animation: mmpop .4s cubic-bezier(.2, 1.2, .4, 1) forwards; font-style: normal; }
.sheet__title { font: 600 27px var(--font-display); color: var(--ink-2); }
.sheet__sub { font: 700 13px var(--font-ui); color: #7a6a94; margin-bottom: var(--s4); }
.sheet__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; margin-bottom: var(--s4); }
.sheet__stat { background: #fff; border-radius: 16px; padding: 11px; }
.sheet__stat b { display: block; font: 800 19px var(--font-ui); color: var(--ink-2); }
.sheet__stat span { font: 700 11px var(--font-ui); color: #9186a8; }
.sheet__unlock {
  background: rgba(52, 209, 192, .14);
  border-radius: 14px; padding: 9px;
  font: 700 12px var(--font-ui); color: #0e7a6f;
  margin-bottom: 14px;
}
.sheet__cta {
  display: block; width: 100%;
  background: linear-gradient(180deg, var(--cta-2), var(--cta));
  border-radius: var(--r-tile);
  padding: 15px;
  font: 600 21px var(--font-display); color: #5b2c00;
  box-shadow: 0 6px 0 var(--cta-shadow);
  transition: transform .12s ease;
}
.sheet__cta:active { transform: translateY(4px); box-shadow: 0 2px 0 var(--cta-shadow); }
.sheet__row { display: flex; gap: 10px; margin-top: 10px; }
.sheet__ghost {
  flex: 1;
  border: 2px solid rgba(42, 19, 80, .15);
  border-radius: 16px; padding: 12px;
  font: 800 13px var(--font-ui); color: var(--ink-2);
  transition: background .15s ease;
}
.sheet__ghost:hover { background: rgba(42, 19, 80, .06); }

/* --------------------------------------------------------------------------
   Trophy room
   -------------------------------------------------------------------------- */

.badges { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 11px; }

/* The table trophies are the record of the path, so they get their own shelf
   and a tighter grid — nine of them should read as one row of a collection,
   not as nine separate achievements. */
.badges--tables { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.badges--tables .badge { padding: 11px 7px; border-radius: 18px; }
.badges--tables .badge__desc { display: none; }

.badges__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3);
  margin: var(--s4) 2px var(--s2);
}
.badges__head:first-child { margin-top: 2px; }
.badges__title {
  font: 600 17px var(--font-display);
  color: #fff;
  margin: 0;
}
.badges__count {
  font: 800 12px var(--font-ui);
  color: var(--gold);
}
.badge {
  border-radius: 22px;
  padding: 14px 12px;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  text-align: center;
  background: rgba(255, 255, 255, .07);
  border: 1.5px solid rgba(255, 255, 255, .12);
  opacity: .42;
}
.badge.is-on {
  opacity: 1;
  background: linear-gradient(180deg, rgba(255, 215, 106, .26), rgba(255, 140, 60, .14));
  border-color: rgba(255, 215, 106, .5);
}
.badge.is-new { animation: mmpop .5s cubic-bezier(.2, 1.2, .4, 1) both; }
.badge__icon { font-size: 30px; line-height: 1; }
.badge__name { font: 800 13px var(--font-ui); color: #fff; }
.badge__desc { font: 600 10.5px/1.35 var(--font-ui); color: rgba(255, 255, 255, .6); }

/* --------------------------------------------------------------------------
   Brain map (progress)
   -------------------------------------------------------------------------- */

.brain__counts .stat { text-align: center; border-radius: var(--r-tile); padding: 13px; }
.brain__counts .stat__value { font-size: 24px; }
.brain__counts .stat__label { font-size: 10.5px; color: rgba(255, 255, 255, .7); }
.stat--mastered { background: rgba(18, 176, 108, .2); }
.stat--mastered .stat__value { color: var(--green-soft); }
.stat--learning { background: rgba(255, 182, 46, .2); }
.stat--learning .stat__value { color: var(--gold); }
.stat--practice { background: rgba(255, 133, 185, .2); }
.stat--practice .stat__value { color: #ff9fc7; }

.section-label {
  font: 800 12px var(--font-ui);
  color: rgba(255, 255, 255, .55);
  letter-spacing: .08em;
}

.facts { display: flex; flex-direction: column; gap: 8px; }
.fact {
  display: flex; align-items: center; gap: 11px;
  background: rgba(255, 255, 255, .09);
  border-radius: 16px;
  padding: 10px 13px;
}
.fact .eq { font-size: 16px; min-width: 96px; }
/* Softer equation palette against the dark panel */
.fact .eq__a { color: #ff8fc0; }
.fact .eq__op { color: #ffb072; }
.fact .eq__b { color: #8ac6ff; }
.fact .eq__equals { color: #6ee0ac; }
.fact .eq__ans { color: #c79cff; }
.fact__bar { flex: 1; height: 9px; border-radius: 6px; background: var(--well); overflow: hidden; }
.fact__fill { height: 100%; border-radius: 6px; transition: width .5s ease; }
.fact__label { font: 800 11px var(--font-ui); min-width: 62px; text-align: right; }
.is-mastered { color: var(--green-soft); }
.is-learning { color: var(--gold); }
.is-practice { color: #ff9fc7; }

.empty {
  border: 1.5px dashed rgba(255, 255, 255, .2);
  border-radius: var(--r-tile);
  padding: var(--s5);
  text-align: center;
  font: 600 13px/1.6 var(--font-ui);
  color: rgba(255, 255, 255, .6);
}

/* --------------------------------------------------------------------------
   Settings
   -------------------------------------------------------------------------- */

.toggle {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%;
  background: rgba(255, 255, 255, .1);
  border-radius: 18px;
  padding: 14px 16px;
  min-height: var(--tap);
  text-align: left;
}
.toggle:hover { background: rgba(255, 255, 255, .15); }
.toggle__name { font: 800 14px var(--font-ui); color: #fff; }
.toggle__track {
  width: 50px; height: 29px;
  border-radius: 16px; padding: 3px;
  background: rgba(255, 255, 255, .22);
  display: flex; justify-content: flex-start;
  flex: none;
  transition: background .2s ease;
}
.toggle[aria-checked="true"] .toggle__track { background: var(--teal); justify-content: flex-end; }
.toggle__knob {
  width: 23px; height: 23px;
  border-radius: 50%; background: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .25);
}
.danger {
  border: 1.5px solid rgba(255, 140, 170, .4);
  background: rgba(255, 90, 140, .12);
  color: #ffc3d6;
  border-radius: 18px; padding: 14px 16px;
  font: 800 13px var(--font-ui);
  min-height: var(--tap);
}
.danger:hover { background: rgba(255, 90, 140, .2); }

/* --------------------------------------------------------------------------
   The typed-name dialog

   Everything irreversible goes through this. It is deliberately not pretty:
   no bounce, no gold, no duck. The child's app is full of things that invite a
   tap, and this is the one screen that should feel like paperwork.
   -------------------------------------------------------------------------- */
.overlay--danger { background: rgba(8, 4, 20, .82); }
.dangerous {
  background: linear-gradient(180deg, #2a2142, #201936);
  border: 1.5px solid rgba(255, 140, 170, .35);
  text-align: left;
  animation: none;
}
.dangerous__mark { font-size: 30px; line-height: 1; text-align: center; }
.dangerous__title {
  font: 700 21px/1.25 var(--font-display);
  color: #fff;
  margin: 8px 0 10px;
  text-align: center;
}
.dangerous__lede {
  font: 700 13px/1.5 var(--font-ui);
  color: rgba(255, 255, 255, .8);
  margin: 0 0 8px;
}
.dangerous__list {
  margin: 0 0 12px;
  padding-left: 20px;
  font: 600 13px/1.65 var(--font-ui);
  color: rgba(255, 255, 255, .72);
}
.dangerous__list li { margin-bottom: 3px; }
.dangerous__keeping {
  font: 600 12.5px/1.55 var(--font-ui);
  color: var(--green-soft);
  background: rgba(92, 240, 174, .1);
  border-radius: var(--r-tile);
  padding: 10px 12px;
  margin: 0 0 12px;
}
.dangerous__final {
  font: 800 13px/1.5 var(--font-ui);
  color: #ffc3d6;
  margin: 0 0 var(--s4);
}
.dangerous__ask {
  display: flex; flex-direction: column; gap: 7px;
  font: 800 12px var(--font-ui);
  color: rgba(255, 255, 255, .72);
  margin-bottom: var(--s4);
}
.dangerous__input { text-align: center; }
.dangerous__row { display: flex; gap: 10px; }
.dangerous__row > * { flex: 1 1 0; min-height: var(--tap); }
/* Keeping the child's work is the easy, obvious choice, so it is the solid
   button and the destructive one is the outline. .sheet__ghost is built for
   the cream results sheet — inherited onto this dark card it rendered as dark
   text on a dark ground, which made "Keep it" the hardest thing on screen to
   find. On a dialog about losing a year of work, cancel is never the quiet
   option. */
.dangerous__cancel {
  border: 2px solid rgba(255, 255, 255, .3);
  background: rgba(255, 255, 255, .14);
  color: #fff;
  border-radius: 16px;
  padding: 12px;
  font: 800 14px var(--font-ui);
}
.dangerous__cancel:hover { background: rgba(255, 255, 255, .22); }
.dangerous__go { text-align: center; }

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

/* --------------------------------------------------------------------------
   Parent dashboard — the one light screen
   -------------------------------------------------------------------------- */

/* The dashboard used to be the one light screen in the app — white cards on
   paper, built when it was a grown-up's corner of a child's game. It now runs
   on the same purple ground as everything else: lit panels, gold labels, and
   the two verdict colours pushed bright enough to read on a dark card.

   The numbers are the point of this screen, so they stay the loudest thing on
   it — everything else is deliberately quieter than the auth panels. */

.parent { color: #fff; }
.parent .topbar__title { font: 800 18px var(--font-ui); color: #fff; }
.parent__period {
  font: 600 11px var(--font-mono);
  color: var(--gold);
  letter-spacing: .04em;
  opacity: .75;
}

/* The child's play code, directly under the title. Quiet by design — it is a
   reference, not a number the parent came for — but it is a real tap target,
   because the whole point is getting it onto another device. */
.p-code {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: 9px 12px;
  border-radius: var(--r-pill);
  background: rgba(0, 0, 0, .26);
  border: 1px solid rgba(255, 255, 255, .13);
  text-align: left;
  transition: background .15s ease, border-color .15s ease;
}
/* [hidden] is a UA rule (display:none), and the display:flex above outranks
   it — the same trap as .kidcard in auth.css. Say it explicitly. */
.p-code[hidden] { display: none; }
.p-code:hover { background: rgba(0, 0, 0, .34); border-color: rgba(255, 255, 255, .22); }
.p-code:active { transform: scale(.995); }
.p-code__label {
  font: 800 10px var(--font-ui);
  color: var(--gold);
  letter-spacing: .06em;
  text-transform: uppercase;
  opacity: .8;
}
.p-code__value {
  font: 500 14px 'IBM Plex Mono', monospace;
  letter-spacing: .08em;
  color: #fff;
  user-select: all;
}
.p-code__note {
  margin-left: auto;
  font: 600 10.5px var(--font-ui);
  color: rgba(255, 255, 255, .45);
  white-space: nowrap;
}

.p-card {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .13), rgba(255, 255, 255, 0) 46%),
    linear-gradient(165deg, rgba(91, 58, 214, .5), rgba(36, 16, 86, .62));
  border: 1px solid rgba(255, 255, 255, .15);
  border-radius: 16px;
  padding: 14px;
  box-shadow: 0 8px 22px rgba(0, 0, 0, .22);
}
.p-card__title {
  font: 800 12px var(--font-ui);
  color: var(--gold);
  letter-spacing: .05em;
  text-transform: uppercase;
  margin-bottom: 10px;
}

/* The headline figures. Bigger than they were: on the light card they only
   had to beat white, here they carry the screen. */
.p-stat b { display: block; font: 800 24px var(--font-ui); color: #fff; }
.p-stat span { font: 600 11px var(--font-ui); color: rgba(255, 255, 255, .58); }

.p-trend { display: flex; align-items: flex-end; gap: 7px; height: 92px; }
.p-trend__col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 5px; justify-content: flex-end; }
.p-trend__bar {
  width: 100%;
  border-radius: 5px 5px 2px 2px;
  background: linear-gradient(180deg, var(--teal-2), var(--teal-3));
  box-shadow: 0 0 14px rgba(52, 209, 192, .35);
  transition: height .5s ease;
}
/* A day with no practice. Visible enough to show the gap, dim enough that it
   never reads as a bad score. */
.p-trend__col.is-empty .p-trend__bar {
  background: rgba(255, 255, 255, .1);
  box-shadow: none;
}
.p-trend__day { font: 600 9.5px var(--font-mono); color: rgba(255, 255, 255, .5); }

.p-tags { display: flex; flex-wrap: wrap; gap: 7px; }
.p-tag {
  border-radius: 9px; padding: 6px 10px;
  font: 700 12px var(--font-mono);
}
/* Pink and green rather than red and green: this is a child's practice list,
   not a test result, and "needs work" should not look like a failure. */
.p-tag--weak   { background: rgba(255, 133, 185, .18); border: 1px solid rgba(255, 133, 185, .4); color: var(--pink-soft); }
.p-tag--strong { background: rgba(92, 240, 174, .15); border: 1px solid rgba(92, 240, 174, .35); color: var(--green-soft); }

.p-hint { font: 600 11px/1.5 var(--font-ui); color: rgba(255, 255, 255, .5); margin-top: 10px; }

/* ---- A child who has not started ----------------------------------------
   The frame around a report of zeroes, not a replacement for it. Quiet on
   purpose: it is context, and the screen below it is the point. The gold left
   edge is the one thing that marks it as a state rather than another card. */
.p-fresh { border-left: 3px solid var(--gold); }
.p-fresh__lede {
  font: 800 14px/1.45 var(--font-ui);
  color: #fff;
  margin-top: 6px;
}

/* ---- The two irreversible things ----------------------------------------
   Reset and Remove, walled off at the bottom of the report. The card exists to
   put a border and a heading between them and the prize card above: a parent
   scrolling to the end of their child's week is not shopping for either, and
   a bare red button one thumb-width from "Add a prize" is how a child loses a
   year of stars. Full width and stacked, so neither can be hit by aiming at
   the other. */
.p-danger {
  display: flex; flex-direction: column; gap: 10px;
  border-color: rgba(255, 140, 170, .28);
}
.p-danger .p-card__title { color: #ffc3d6; }
.p-danger .danger { width: 100%; }
.p-danger .p-hint { margin-top: 0; }

/* Removing the child is the worse of the two and is meant to look it — filled
   rather than outlined, so the pair never reads as one row of equal choices. */
.danger--hard {
  background: rgba(255, 90, 140, .26);
  border-color: rgba(255, 140, 170, .6);
  color: #fff;
}
.danger--hard:hover { background: rgba(255, 90, 140, .38); }
.danger[disabled] { opacity: .55; pointer-events: none; }
.p-activity { display: flex; flex-direction: column; gap: 8px; }
.p-activity__row {
  display: flex; justify-content: space-between; gap: var(--s3);
  font: 600 12px var(--font-ui); color: rgba(255, 255, 255, .82);
}
.p-activity__when { color: rgba(255, 255, 255, .45); white-space: nowrap; }
.p-empty {
  font: 600 12px/1.6 var(--font-ui); color: rgba(255, 255, 255, .55);
  text-align: center; padding: var(--s4);
}

/* --------------------------------------------------------------------------
   Toast (badge unlocked)
   -------------------------------------------------------------------------- */

.toasts {
  position: fixed;
  left: 50%; bottom: 22px;
  transform: translateX(-50%);
  display: flex; flex-direction: column; gap: 8px;
  z-index: 80;
  width: min(420px, calc(100vw - 32px));
  pointer-events: none;
}
.toast {
  background: linear-gradient(180deg, #fffdf6, #ffeed4);
  border-radius: 18px;
  padding: 12px 16px;
  display: flex; align-items: center; gap: 10px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, .35);
  animation: mmpop .4s cubic-bezier(.2, 1.2, .4, 1) both;
}
.toast__icon { font-size: 24px; }
.toast__title { font: 800 13px var(--font-ui); color: var(--ink-2); }
.toast__desc { font: 600 11px var(--font-ui); color: #7a6a94; }

/* --------------------------------------------------------------------------
   Larger screens — give the board more room
   -------------------------------------------------------------------------- */

@media (min-width: 700px) {
  .screen { padding: var(--s6) var(--s6) 36px; gap: var(--s5); }
  .board { --gap: 16px; }
}
@media (min-width: 900px) {
  .app-main { max-width: 620px; }
}

/* ==========================================================================
   Quackers' additions
   ========================================================================== */

/* Home has two more doors than it used to, so it is allowed to scroll on a
   short phone. The x axis stays clipped — the corner glow sits at
   right:-70px and would otherwise widen the page. */
.screen--home { overflow-x: hidden; overflow-y: auto; }
.home__hero { min-height: 128px; }

/* The second call to action. Deliberately a different shape and colour from
   PLAY, so a child never has to read to tell them apart. */
.cta--quiz {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  min-height: 62px;
  padding: 8px 16px 8px 8px;
  border-radius: var(--r-pill);
  background: linear-gradient(100deg, #ff3d8b, #8b3dff 62%, #1e8fff);
  color: #fff;
  font: 600 21px var(--font-display);
  letter-spacing: .01em;
  box-shadow: 0 7px 0 #5a1f8c, 0 16px 30px rgba(139, 61, 255, .45);
  transition: transform .15s ease, box-shadow .15s ease;
}
.cta--quiz:hover  { transform: translateY(-2px); box-shadow: 0 9px 0 #5a1f8c, 0 20px 36px rgba(139,61,255,.5); }
.cta--quiz:active { transform: translateY(5px);  box-shadow: 0 2px 0 #5a1f8c; }
.cta__duck {
  display: grid; place-items: center;
  width: 50px; height: 50px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .2);
  flex: none;
  overflow: hidden;
}
.cta__duck .dk { margin-top: 6px; }
.cta__tag {
  display: inline-flex; align-items: center; gap: 4px;
  margin-left: auto;
  white-space: nowrap;
  flex: none;
  font: 800 11px var(--font-ui);
  background: rgba(0, 0, 0, .25);
  border-radius: 99px;
  padding: 4px 9px;
  letter-spacing: .06em;
}
.cta__tag-icon { color: #ffd76a; letter-spacing: 0; }
/* On a phone the button's own name would wrap to two lines to make room for
   this. BEST is the word that can go; SCORE is the one doing the work. */
@media (max-width: 430px) {
  .cta__tag-best { display: none; }
  .cta__tag { font-size: 10px; padding: 4px 6px; gap: 3px; }
  /* Buys back the pixels the label needs to stay on one line at 360px. */
  .cta--quiz { gap: 8px; }
}

.link-tile__dot {
  margin-left: auto;
  min-width: 22px;
  padding: 2px 6px;
  border-radius: 99px;
  background: var(--gold);
  color: #4a2c00;
  font: 800 12px var(--font-ui);
  text-align: center;
}

/* The invitation to the quiz, on the level-complete sheet */
.sheet__quiz {
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  margin-top: 10px;
  padding: 8px 14px 8px 8px;
  border-radius: var(--r-pill);
  background: linear-gradient(100deg, rgba(255, 61, 139, .14), rgba(139, 61, 255, .16));
  border: 2px solid rgba(139, 61, 255, .35);
  color: var(--ink-2);
  font: 800 14px var(--font-ui);
  min-height: var(--tap);
  transition: transform .15s ease, background .15s ease;
}
.sheet__quiz:hover  { transform: translateY(-2px); background: linear-gradient(100deg, rgba(255,61,139,.22), rgba(139,61,255,.24)); }
.sheet__quiz:active { transform: translateY(1px); }
.sheet__quiz-duck {
  display: grid; place-items: center;
  width: 42px; height: 42px;
  border-radius: 50%;
  background: #fff;
  flex: none; overflow: hidden;
}
.sheet__quiz-duck .dk { margin-top: 5px; }

.settings__duck { margin-bottom: -6px; }

.screen--qdone { justify-content: center; overflow-x: hidden; overflow-y: auto; }
.screen--qdone .qres { margin-block: auto; width: 100%; }
.screen--qdone .qres .dk-stage { margin-top: 44px; }
.screen--qdone .sheet__stat { background: rgba(255, 255, 255, .12); }
.screen--qdone .sheet__stat b { color: #fff; }
.screen--qdone .sheet__stat span { color: rgba(255, 255, 255, .6); }
.screen--qdone .sheet__ghost { background: var(--sheer-2); color: #fff; }
.screen--qdone .sheet__ghost:hover { background: var(--sheer-3); }

.screen--pond { overflow-x: hidden; }
.screen--pond .pond__hero { margin-top: 40px; }

/* Quackers on the level-complete sheet: the win needs a face on it. */
.sheet__duck { margin: -6px auto 2px; }

/* --------------------------------------------------------------------------
   Settings — sections

   One flat column became six labelled groups. The headings are not decoration:
   they are what tells a child (and a parent on a phone, scrolling fast) that
   they have left one kind of setting and arrived at another. The screen used
   to run Calm mode straight into "Reset all progress" with nothing in between.
   -------------------------------------------------------------------------- */
.settings__body { display: flex; flex-direction: column; gap: var(--s5); }
.settings__section { display: flex; flex-direction: column; gap: 10px; }
.settings__heading { margin: 0 2px -2px; }
.settings__toggles { display: flex; flex-direction: column; gap: 10px; }
.settings__foot {
  font: 600 12px/1.6 var(--font-ui);
  color: rgba(255, 255, 255, .45);
  text-align: center;
  margin: 2px 8px 0;
}

/* The lesson check's verdict on the results sheet. Deliberately a quiet row
   rather than a banner: it is information, not a judgement, and the
   celebration above it belongs to the board the child just cleared either way.

   Inked for the SHEET, which is the one light surface in the app — the first
   pass of this used the app's white-on-dark text and came out as a pale ghost
   on cream, legible only if you already knew what it said. */
.sheet__check {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 10px var(--s3);
  margin-bottom: 12px;
  border-radius: 16px;
  text-align: left;
  background: rgba(255, 182, 46, .16);
  border: 1.5px solid rgba(255, 182, 46, .5);
}
.sheet__check.is-on {
  background: rgba(18, 176, 108, .13);
  border-color: rgba(18, 176, 108, .45);
}
.sheet__check-icon { font-size: 22px; line-height: 1; flex: none; }
.sheet__check-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.sheet__check-text b { font: 800 13px var(--font-ui); color: var(--ink-2); }
.sheet__check-text span { font: 600 12px/1.4 var(--font-ui); color: var(--muted); }

.sheet__ghost.is-urged {
  background: rgba(255, 171, 29, .2);
  border: 1.5px solid var(--cta);
  color: #8a5200;
}

/* The check wears the same chip as a table test, in a calmer colour: it is a
   question, not a gate. */
.quiz__testchip--check {
  background: linear-gradient(140deg, var(--teal-2), var(--teal-3));
  color: #05312c;
}
