/* ============================================================
   VERSE QUEST — structural stylesheet
   Theme-agnostic. Every colour, font, radius and border comes
   from the semantic roles defined in themes/*.css.
   One markup tree, two skins.
   ============================================================ */

*{box-sizing:border-box;margin:0;padding:0}
/* Any rule that sets an explicit `display` outranks the UA's [hidden]
   rule, so hidden elements would still show. Settle it once, up front. */
[hidden]{display:none!important}
html,body{height:100%}
body{
  background:var(--bg);
  background-attachment:fixed;
  color:var(--text);
  font-family:var(--font-ui);
  -webkit-font-smoothing:antialiased;
  overscroll-behavior:none;
}
/* Fixed to the viewport, not min-height: the quiz screen has to be able to
   pin its grade buttons to the bottom and let a long verse scroll inside the
   card. Screens that need to scroll do it internally via .scroll. */
#app{
  max-width:520px;margin:0 auto;height:100dvh;
  display:flex;flex-direction:column;position:relative;z-index:1;
}

/* ---------- screens ---------- */
/* Each theme paints its own atmosphere into .screen::before — a track sweep
   for racing, cloud puffs for princess. Content rides above it. */
.screen{display:none;flex:1;flex-direction:column;min-height:0;position:relative;overflow:hidden}
.screen.is-active{display:flex}
.screen > *{position:relative;z-index:1}
.pad{padding:20px 18px 28px}
.scroll{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch}
.center-col{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;text-align:center}

/* ---------- top bar ---------- */
.bar{
  display:flex;align-items:center;gap:10px;padding:14px 14px 10px;flex:none;
}
.bar-title{
  flex:1;text-align:center;font-family:var(--font-display);font-size:19px;
  font-weight:700;color:var(--heading);transform:skewX(var(--skew));
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.bar-count{
  font-family:var(--font-display);font-size:13px;font-weight:700;color:var(--accent-ink);
  background:var(--accent);border:var(--bw) solid var(--bc);border-radius:var(--r-pill);
  padding:3px 10px;min-width:44px;text-align:center;
}
.icon-btn{
  width:38px;height:38px;flex:none;border-radius:var(--r-btn-round);
  background:var(--surface);border:var(--bw) solid var(--bc);
  color:var(--heading);font-size:17px;line-height:1;font-weight:700;
  display:grid;place-items:center;cursor:pointer;box-shadow:var(--shadow);
}
.icon-btn:active{transform:translateY(1px)}

/* ---------- type ---------- */
.brand{
  font-family:var(--font-display);font-size:40px;line-height:1.05;font-weight:700;
  color:var(--heading);transform:skewX(var(--skew));
  background:var(--brand-fill);-webkit-background-clip:text;background-clip:text;
  color:var(--brand-color);
}
.lede{font-size:15px;color:var(--text-soft);font-weight:600}
.hint{font-size:13px;color:var(--text-soft);margin-bottom:14px;line-height:1.5}
.sect{display:flex;align-items:center;gap:10px;margin:22px 0 10px}
.sect span{font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--heading);font-weight:700}
.sect i{flex:1;height:3px;border-radius:3px;background:var(--rule);opacity:.55}

/* ---------- generic surfaces ---------- */
.card{
  background:var(--surface);border:var(--bw) solid var(--bc);
  border-radius:var(--r-lg);box-shadow:var(--shadow);
}

/* ---------- who picker ---------- */
.who-grid{display:flex;flex-direction:column;gap:14px;width:100%;max-width:300px;margin-top:8px}
.who-btn{
  padding:22px 18px;border-radius:var(--r-lg);cursor:pointer;
  font-family:var(--font-display);font-size:26px;font-weight:700;
  border:var(--bw) solid var(--bc);box-shadow:var(--shadow-lg);
  display:flex;align-items:center;justify-content:center;gap:12px;
  transform:skewX(var(--skew));
}
.who-btn .em{font-size:28px;transform:skewX(calc(-1 * var(--skew)))}
.who-btn span.nm{transform:skewX(calc(-1 * var(--skew)))}
.who-btn:active{transform:skewX(var(--skew)) translateY(2px)}
.who-btn.t-princess{background:var(--who-a-bg);color:var(--who-a-fg)}
.who-btn.t-racing{background:var(--who-b-bg);color:var(--who-b-fg)}

/* ---------- home ---------- */
.home-art{flex:none;margin-bottom:4px}
.home-count{
  font-family:var(--font-display);font-size:64px;line-height:.9;font-weight:700;
  color:var(--heading);transform:skewX(var(--skew));
}
.home-count-l{
  font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--text-soft);
  font-weight:700;margin-top:2px;
}
.home-actions{width:100%;max-width:300px;margin-top:20px}
.btn-hero{font-size:21px;padding:18px 20px}

/* ---------- the journey ---------- */
/* The strip on home: where you are, and how far to the next place. */
.journey-strip{
  display:flex;align-items:center;gap:12px;width:100%;max-width:320px;
  padding:12px 14px;cursor:pointer;text-align:left;
  background:var(--surface);border:var(--bw) solid var(--bc);
  border-radius:var(--r-lg);box-shadow:var(--shadow);
  transition:transform .16s var(--ease);
}
.journey-strip:hover{transform:translateY(-2px)}
.journey-strip:active{transform:translateY(1px)}
.js-art{flex:none;line-height:0}
.js-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:5px}
.js-stage{font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--heading);
  transform:skewX(var(--skew));white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.js-bar{display:block;height:7px;border-radius:4px;background:var(--track);
  border:var(--bw-thin) solid var(--bc-soft);overflow:hidden}
.js-bar i{display:block;height:100%;background:var(--fill);transition:width .4s var(--ease)}
.js-next{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--text-soft);font-weight:700}

/* The screen: the goal on top, the stages running down from it. */
.j-top{display:flex;flex-direction:column;align-items:center;gap:6px;text-align:center;
  padding-bottom:18px}
.j-emblem{line-height:0;filter:grayscale(.55) opacity(.75)}
/* Only lit once it's actually been reached — a bright castle you haven't
   climbed to is a promise the screen hasn't kept. */
.j-emblem.is-reached{filter:none;animation:twinkle 2.6s ease-in-out infinite}
.j-goal{font-family:var(--font-display);font-size:17px;font-weight:700;color:var(--heading);
  transform:skewX(var(--skew))}
.j-tally{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-soft);font-weight:700}

.j-replayhint{
  font-size:11px;color:var(--text-soft);font-weight:600;margin-top:8px;
  padding:6px 12px;border-radius:var(--r-pill);background:var(--surface-alt);
}

/* ---------- the map ---------- */
.j-map{position:relative;width:100%;margin:18px 0 6px}
.j-svg{position:absolute;left:0;top:0;overflow:visible}
.j-ground{fill:var(--map-ground,transparent)}
.j-mountain{fill:var(--map-hill,#E3DCEA)}
.j-snow{fill:var(--map-peak,#FFFFFF)}
.j-sector{stroke:var(--bc-soft);stroke-width:1;stroke-dasharray:3 9}
/* The route: the whole thing faint, the part already walked drawn over it. */
.j-route{fill:none;stroke:var(--track);stroke-width:7;stroke-linecap:round}
.j-route-done{fill:none;stroke:var(--map-route,var(--dot-done));stroke-width:7;stroke-linecap:round;
  transition:stroke-dashoffset .6s var(--ease)}

.j-pin{
  position:absolute;transform:translate(-50%,-50%);
  display:flex;flex-direction:column;align-items:center;gap:4px;
  background:none;border:0;padding:0;cursor:default;width:96px;
}
.j-pin.is-past,.j-pin.is-now{cursor:pointer}
.j-pin-dot{
  width:38px;height:38px;border-radius:var(--r-btn-round);display:grid;place-items:center;
  font-family:var(--font-display);font-size:15px;font-weight:700;
  background:var(--track);color:var(--text-soft);
  border:var(--bw) solid var(--bc-soft);box-shadow:var(--shadow);
}
.j-pin.is-past .j-pin-dot{background:var(--knew-bg);color:var(--knew-fg);border-color:var(--bc)}
.j-pin.is-now .j-pin-dot{background:var(--accent);color:var(--accent-ink);border-color:var(--bc);
  box-shadow:var(--shadow),var(--dot-glow)}
.j-pin-nm{
  font-family:var(--font-display);font-size:11px;font-weight:700;line-height:1.15;
  color:var(--heading);text-align:center;transform:skewX(var(--skew));
  text-shadow:0 1px 0 var(--surface),0 0 6px var(--surface);
}
.j-pin.is-todo .j-pin-nm{color:var(--text-soft)}
.j-pin-play{
  position:absolute;top:-6px;right:4px;width:19px;height:19px;border-radius:var(--r-btn-round);
  display:grid;place-items:center;font-size:8px;
  background:var(--chip-bg);color:var(--chip-fg);border:var(--bw-thin) solid var(--bc-soft);
}
.j-pin:active .j-pin-dot{transform:translateY(1px)}
.j-pin:focus-visible{outline:3px solid var(--focus);outline-offset:3px;border-radius:8px}

/* Whoever is walking it, sitting on the route where the drawing stops. */
.j-walker{
  position:absolute;width:56px;transform:translate(-50%,-62%);line-height:0;pointer-events:none;
  filter:drop-shadow(0 4px 7px rgba(0,0,0,.35));
}
.j-walker svg{width:100%;height:auto;display:block}

/* You are here. */
.j-here{
  margin-top:14px;padding:14px 16px;text-align:center;
  background:var(--surface);border:var(--bw) solid var(--bc);
  border-radius:var(--r-lg);box-shadow:var(--shadow);
}
.j-here-l{font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--text-soft);font-weight:700}
.j-here-n{font-family:var(--font-display);font-size:20px;font-weight:700;color:var(--heading);
  transform:skewX(var(--skew));margin-top:3px}
.j-here-b{font-size:12.5px;color:var(--text-soft);line-height:1.45;margin-top:3px}
.j-bar{display:block;height:8px;border-radius:5px;background:var(--track);
  border:var(--bw-thin) solid var(--bc-soft);overflow:hidden;margin:10px 0 6px}
.j-bar i{display:block;height:100%;background:var(--fill);transition:width .4s var(--ease)}
.j-need{font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--text-soft);font-weight:700}
.j-here-play{margin-top:12px;font-size:13px;padding:10px 16px}

/* The garage shelf — what the Perfects have actually won. */
.j-shelf{margin-top:26px}
.j-cars{display:grid;grid-template-columns:repeat(2,1fr);gap:9px}
.j-car{
  display:flex;flex-direction:column;align-items:center;gap:5px;padding:11px 8px;
  background:var(--surface-alt);border:var(--bw-thin) solid var(--bc-soft);
  border-radius:var(--r-md);opacity:.45;
}
.j-car.is-won{opacity:1;background:var(--surface);border-color:var(--bc);box-shadow:var(--shadow)}
.j-car-art{width:52px;height:26px;display:grid;place-items:center;
  font-family:var(--font-display);font-size:19px;color:var(--text-soft)}
.j-car-art svg{width:100%;height:auto}
.j-car-nm{font-size:10px;font-weight:700;color:var(--text-soft);text-align:center;
  letter-spacing:.04em}
.j-car.is-won .j-car-nm{color:var(--heading)}

/* Reaching a stage mid-round is bigger news than the score, so it sits
   above it on the results screen. */
.j-ribbon{
  display:flex;flex-direction:column;align-items:center;gap:3px;
  padding:12px 18px;margin-bottom:20px;width:100%;
  background:var(--badge-bg);color:var(--badge-fg);
  border:var(--bw) solid var(--bc);border-radius:var(--r-md);box-shadow:var(--shadow);
}
.j-ribbon-l{font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;font-weight:700;opacity:.8}
.j-ribbon-n{font-family:var(--font-display);font-size:21px;font-weight:700;
  transform:skewX(var(--skew));line-height:1.1}
.j-ribbon-b{font-size:12px;font-weight:600;text-align:center;opacity:.9}

/* ---------- game picker ---------- */
.game-picker{display:flex;flex-direction:column;gap:12px;width:100%;max-width:320px;margin-top:14px}
.game{
  display:flex;flex-direction:column;align-items:center;gap:2px;width:100%;
  padding:16px 14px;cursor:pointer;text-align:center;
  background:var(--surface);border:var(--bw) solid var(--bc);
  border-radius:var(--r-lg);box-shadow:var(--shadow);
  transition:transform .16s var(--ease);
}
.game:hover{transform:translateY(-2px)}
.game:active{transform:translateY(1px)}
.game:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
.game[disabled]{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none}
.game-n{font-family:var(--font-display);font-size:38px;line-height:1;font-weight:700;
  color:var(--heading);transform:skewX(var(--skew))}
.game-nm{font-family:var(--font-display);font-size:18px;font-weight:700;color:var(--heading);
  transform:skewX(var(--skew));text-transform:var(--ref-case)}
.game-sub{font-size:11.5px;color:var(--text-soft);font-weight:600;margin-top:2px}

/* ---------- settings ---------- */
.sgroup{margin-bottom:20px}
.sgroup-head{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.sgroup-nm{flex:1;font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--heading);
  transform:skewX(var(--skew))}
.mini-btn{
  font-size:10.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  padding:5px 10px;border-radius:var(--r-pill);cursor:pointer;
  background:var(--chip-bg);color:var(--chip-fg);border:var(--bw-thin) solid var(--bc);
}
/* Two ticks per row — one per game — so the columns need labelling once
   per pack rather than on every row. */
.sgroup-cols{display:flex;justify-content:flex-end;gap:9px;margin:-2px 13px 6px 0}
.sgroup-cols span{width:34px;text-align:center;font-size:8.5px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--text-soft);font-weight:700}

.vrow{
  display:flex;align-items:center;gap:11px;padding:11px 13px;margin-bottom:7px;
  background:var(--surface);border:var(--bw) solid var(--bc);border-radius:var(--r-md);
  box-shadow:var(--shadow);width:100%;text-align:left;
}
.vr-toggles{display:flex;gap:9px;flex:none}
.vrow .box{
  width:34px;height:34px;flex:none;border-radius:var(--r-check);cursor:pointer;
  border:var(--bw) solid var(--bc);background:var(--surface-alt);
  display:grid;place-items:center;font-size:16px;color:transparent;font-weight:700;
}
.vrow .box[aria-pressed="true"]{background:var(--knew-bg);color:var(--knew-fg)}
.vrow .box:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
.vrow .vr-body{flex:1;min-width:0}
.vrow .vr-ref{font-family:var(--font-display);font-size:14px;font-weight:700;color:var(--heading)}
.vrow .vr-txt{font-size:12px;color:var(--text-soft);line-height:1.4;margin-top:2px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.src-note{font-size:11px;color:var(--text-soft);margin-top:14px;line-height:1.5;text-align:center}

/* ---------- progress strand ---------- */
.strand{display:flex;align-items:center;justify-content:center;gap:7px;padding:6px 18px 2px;flex:none;
  flex-wrap:wrap}
.dot{width:11px;height:11px;border-radius:50%;background:var(--dot-todo);
  border:var(--bw-thin) solid var(--bc-soft)}
.dot.done{background:var(--dot-done);border-color:var(--bc)}
.dot.miss{background:var(--dot-miss);border-color:var(--bc)}
.dot.now{width:16px;height:16px;background:var(--dot-now);border-color:var(--bc);
  box-shadow:var(--dot-glow)}

/* Past ~24 cards a dot each turns into wallpaper, so the strand
   collapses to a single bar. The N/total chip in the bar carries the count. */
.strand--bar{display:block;padding:12px 18px 4px}
.sbar{display:block;height:9px;border-radius:5px;background:var(--track);
  border:var(--bw-thin) solid var(--bc-soft);overflow:hidden}
.sbar i{display:block;height:100%;border-radius:4px;background:var(--fill);transition:width .3s}

/* ---------- "Perfect" celebration ---------- */
/* Particles fly out from the middle of the card. Each theme fills in what
   flies: flame shards for racing, sparkles for princess. */
/* top/height are set per-celebration in JS to match the card's box. */
.burst{position:absolute;left:0;right:0;top:0;height:100%;
  pointer-events:none;z-index:5;overflow:hidden}
/* ---------- princess: the unicorn draws a rainbow ---------- */
/* Her mirror of the finish line: something crosses the card and leaves a
   mark. The unicorn rides the outermost arc (offset-path), the rainbow
   bands draw in behind her, and sparkles pop in her wake. */
.burst .arcsvg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.burst .arcsvg path{
  fill:none;stroke-linecap:round;
  stroke-dasharray:var(--len);stroke-dashoffset:var(--len);
  animation:draw 700ms cubic-bezier(.35,0,.5,1) forwards;
}
@keyframes draw{ to{stroke-dashoffset:0} }

.burst .uni{
  position:absolute;left:0;top:0;width:90px;
  /* offset-rotate:0deg keeps her upright — following the tangent nose-dives
     her at the apex and a small figure stops reading as a unicorn. */
  offset-path:var(--path);offset-distance:0%;offset-rotate:0deg;
  animation:ride 700ms cubic-bezier(.35,0,.5,1) forwards;
}
.burst .uni svg{display:block;width:100%;height:auto;
  filter:drop-shadow(0 3px 5px rgba(110,74,87,.35))}
@keyframes ride{ from{offset-distance:0%} to{offset-distance:100%} }

.burst .pop{
  position:absolute;font-size:19px;line-height:1;
  transform:translate(-50%,-50%) scale(0);opacity:0;
  animation:pop 520ms ease-out forwards;
}
@keyframes pop{
  0%  {transform:translate(-50%,-50%) scale(0);    opacity:0}
  35% {transform:translate(-50%,-50%) scale(1.3);  opacity:1}
  100%{transform:translate(-50%,-50%) scale(.85);  opacity:0}
}
/* ---------- racing: crossing the finish line ---------- */
/* A checkered line drops in, the car runs at it, and the line flashes as
   the car goes through. ~700ms start to finish. */
.burst .fin{
  position:absolute;top:7%;bottom:7%;left:70%;width:15px;border-radius:2px;
  background:conic-gradient(#fff 90deg,#10141C 90deg 180deg,#fff 180deg 270deg,#10141C 270deg);
  background-size:7.5px 7.5px;
  box-shadow:0 0 0 2px rgba(0,0,0,.3),0 0 16px rgba(255,255,255,.4);
  animation:finline 700ms ease-out forwards;
}
/* The car's centre reaches the line at ~47% of the run, so the flash is
   keyed there — and the line holds full opacity until well past it. */
@keyframes finline{
  0%  {opacity:0; transform:scaleY(.35); filter:brightness(1)}
  15% {opacity:1; transform:scaleY(1)}
  42% {filter:brightness(1)}
  50% {filter:brightness(2.6)}
  60% {filter:brightness(1)}
  82% {opacity:1}
  100%{opacity:0}
}

.burst .car{
  position:absolute;top:50%;left:0;width:80px;
  transform:translate(-110px,-50%);
  animation:drive 700ms cubic-bezier(.3,0,.5,1) forwards;
}
.burst .car svg{
  display:block;width:100%;height:auto;
  filter:drop-shadow(0 3px 6px rgba(0,0,0,.5));
  animation:bob 150ms ease-in-out infinite alternate;
}
@keyframes drive{
  from{transform:translate(-110px,-50%)}
  to  {transform:translate(var(--run,460px),-50%)}
}
@keyframes bob{from{transform:translateY(0)} to{transform:translateY(-2px)}}

/* speed trails off the back of the car */
.burst .car::before,.burst .car::after{content:"";position:absolute;right:96%;border-radius:2px}
.burst .car::before{top:26%;width:74px;height:4px;
  background:linear-gradient(90deg,transparent,rgba(255,210,30,.95))}
.burst .car::after{top:62%;width:48px;height:3px;
  background:linear-gradient(90deg,transparent,rgba(255,106,0,.9))}


/* ---------- tap to reveal ---------- */
/* Upcoming phrases stay on screen as dim blocks so the passage keeps its
   shape; each tap turns the next one into readable text. */
.learn-area{flex:1;display:flex;align-items:center;justify-content:center;
  padding:14px 18px;min-height:0}
.learn-card{
  width:100%;max-width:400px;max-height:100%;cursor:pointer;position:relative;
  display:flex;flex-direction:column;gap:12px;padding:24px 22px;
  background:var(--card-bg);border:var(--bw) solid var(--bc);
  border-radius:var(--r-card);box-shadow:var(--shadow-lg);overflow:hidden;
}
.learn-card:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
.learn-ref{font-family:var(--font-display);font-size:19px;font-weight:700;flex:none;
  color:var(--heading);transform:skewX(var(--skew));text-transform:var(--ref-case);
  text-align:center}
.words{
  font-family:var(--font-verse);font-size:19px;line-height:1.85;color:var(--text);
  font-weight:500;overflow-y:auto;min-height:0;overscroll-behavior:contain;
}
.ph{
  display:inline;border-radius:6px;
  background:var(--track);color:transparent;
  box-shadow:0 0 0 2px var(--track);          /* pad the block without moving text */
  transition:color .22s ease,background-color .22s ease,box-shadow .22s ease;
}
.ph.is-on{background:transparent;color:inherit;box-shadow:none}
.ph.is-next{background:var(--knew-bg);box-shadow:0 0 0 2px var(--knew-bg)}
.learn-hint{font-family:var(--font-hint);font-size:var(--hint-size);color:var(--text-soft);
  text-align:center;font-weight:600;flex:none;
  letter-spacing:var(--hint-track);text-transform:var(--hint-case)}
.learn-foot{flex:none;padding:8px 18px 22px;display:flex;flex-direction:column;
  align-items:center;gap:10px}
.learn-progress{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-soft);font-weight:700}
.learn-foot .btn{max-width:320px}

/* Which part of a long passage is on screen. A readout, not a control. */
.part-tag{
  flex:none;align-self:center;margin-top:2px;
  font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;font-weight:700;
  color:var(--text-soft);padding:4px 11px;border-radius:var(--r-pill);
  background:var(--surface-alt);border:var(--bw-thin) solid var(--bc-soft);
}

/* ---------- slider blanks ---------- */
/* Same card as tap-to-reveal, driven by a slider instead of taps. The
   passage is laid out a phrase per line, and a blank is a ruled slot the
   width of the word it hides — so the line never reflows mid-drag and the
   gap reads as something to fill rather than something censored. */
/* A phrase per line, spaced rather than ruled: a rule between lines is
   indistinguishable from the ruled blanks sitting on it. */
.bline{padding:3px 0;word-spacing:.08em}
.bline + .bline{margin-top:5px}
.bw{
  display:inline;border-radius:2px;
  transition:color .18s ease,border-color .18s ease;
}
.bw.is-hidden{
  color:transparent;cursor:pointer;
  /* border-color must be named: currentColor is transparent here. */
  border-bottom:2px solid var(--bc-soft);
}
/* Peeked: one word uncovered on tap, its rule left coloured so you can see
   where the help was needed. */
.bw.is-hidden.is-peek{color:inherit;border-bottom-color:var(--dot-done)}
/* The recitation game hands a word over rather than uncovering one, so its
   hints are never `is-hidden` and would otherwise land as bare text with no
   mark on them at all. Same rule, same colour: wherever a word arrived as
   help instead of memory, the card says so. */
.bw.is-peek{color:inherit;border-bottom:2px solid var(--dot-done)}
/* Said out loud and heard. The opposite of a peek in every way that shows:
   a peek is help arriving before the word, this is the word arriving on its
   own. It uses the "you knew it" role, so the two can never be confused. */
.bw.is-said{
  color:inherit;cursor:default;
  /* Heavier than the rule under a peeked word as well as a different
     colour. In the princess skin the help colour is mint and this is a
     deeper green, which is not on its own a difference a six-year-old
     should have to notice — the extra pixel is what carries it. */
  border-bottom:3px solid var(--said-rule);
  animation:said-in .26s var(--ease,ease) both;
}
@keyframes said-in{
  from{opacity:.25;transform:translateY(1px)}
  to{opacity:1;transform:none}
}
@media (prefers-reduced-motion:reduce){
  .bw.is-said{animation:none}
}
/* Passed over. The matcher only ever walks forwards, so a word left behind
   the cursor cannot be recovered by saying it again — and a child who can't
   tell "I missed that" from "I haven't got there yet" will stop and repeat
   themselves at a word that will never light up. Dashed and in the miss
   colour, so it reads as gone by rather than still to come. */
.bw.is-missed{border-bottom:2px dashed var(--dot-miss)}
/* Where the listening has got to: the word to say now. The strand above the
   card marks the verse you're on in --dot-now; this is the same colour
   saying the same thing one level down.

   A glow rather than a rule, deliberately. Every other state here owns the
   bottom border, and a marker that fought them for it would have to win —
   hiding whether that word was a peek, or missed, at the one moment the
   child is looking straight at it. This stacks instead of replacing. It
   never uncovers the word: knowing where you are is free, and being told
   the word costs the recitation. */
.bw.is-now{border-radius:4px;animation:now-here 1.5s ease-in-out infinite}
@keyframes now-here{
  0%,100%{box-shadow:none}
  50%{box-shadow:var(--dot-glow)}
}
@media (prefers-reduced-motion:reduce){
  /* Still marked, just not moving — this one carries information, so it
     can't be dropped the way the celebratory animations can. */
  .bw.is-now{animation:none;box-shadow:var(--dot-glow)}
}
.bw:focus-visible{outline:3px solid var(--focus);outline-offset:2px}

/* ---------- listening ---------- */
/* The microphone control. One button, and its own state is the whole
   readout — there is nothing else on screen saying whether it is on. */
.mic-row{display:flex;align-items:center;gap:10px;width:100%;max-width:340px;
  flex-direction:column;align-self:center}
.mic-row--big{margin-bottom:2px}
.mic-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:10px 18px;cursor:pointer;
  border:var(--bw-thin) solid var(--bc);border-radius:var(--r-pill);
  background:var(--surface);color:var(--heading);
  font-family:var(--font-display);font-size:14px;font-weight:700;
  text-transform:var(--btn-case);letter-spacing:var(--btn-track);
  box-shadow:var(--shadow);
}
.mic-btn--big{padding:15px 30px;font-size:17px;width:100%;max-width:320px}
.mic-btn:active{transform:translateY(2px)}
.mic-btn:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
.mic-btn[disabled]{opacity:.45;cursor:not-allowed}
/* Live. The dot pulses because a microphone that is on and a microphone
   that is off must not be a matter of reading the label. */
.mic-btn.is-live{background:var(--knew-bg);color:var(--knew-fg);
  box-shadow:var(--shadow),var(--knew-glow)}
.mic-dot{
  width:10px;height:10px;border-radius:50%;flex:none;
  background:var(--bc-soft);
}
.mic-btn.is-live .mic-dot{background:currentColor;animation:mic-pulse 1.15s ease-in-out infinite}
@keyframes mic-pulse{
  0%,100%{opacity:.35;transform:scale(.82)}
  50%{opacity:1;transform:scale(1.15)}
}
@media (prefers-reduced-motion:reduce){
  .mic-btn.is-live .mic-dot{animation:none;opacity:1}
}
/* What it thinks it just heard. Deliberately quiet and deliberately present:
   when recognition goes wrong this is the only way to see why. */
/* Full width and fixed height on purpose: sized to its content it would
   grow and shrink with every word heard, twitching the whole footer. */
.mic-status,.heard{
  font-size:11px;color:var(--text-soft);line-height:1.4;text-align:center;
  height:1.4em;font-style:italic;align-self:stretch;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.heard{max-width:340px;margin:0 auto 2px}
.recite-actions{display:flex;flex-direction:column;gap:8px;width:100%;
  max-width:320px;align-items:stretch}
.recite-actions .btn{margin-top:0}

/* The notch's name — the actual readout of the control. */
.blank-level{
  font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--heading);
  transform:skewX(var(--skew));text-align:center;
}
.slider-row{display:flex;align-items:center;gap:11px;width:100%;max-width:340px}
.slider-end{font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--text-soft);font-weight:700;flex:none}
/* A range input is unstyleable without stripping it first, and the track and
   thumb need writing twice — WebKit and Gecko name the parts differently.
   --pct is set from JS so the track fills up to the thumb. */
.slider{flex:1;min-width:0;height:34px;cursor:pointer;background:none;--pct:100%;
  -webkit-appearance:none;appearance:none}
/* Filled portion, then the six notch marks, then the empty track. */
.slider::-webkit-slider-runnable-track{
  height:9px;border-radius:5px;
  border:var(--bw-thin) solid var(--bc-soft);
  background:
    linear-gradient(90deg,var(--dot-done) 0 var(--pct),transparent var(--pct)),
    repeating-linear-gradient(90deg,transparent 0 calc(20% - 1px),var(--bc-soft) calc(20% - 1px) 20%),
    var(--track);
}
.slider::-webkit-slider-thumb{
  -webkit-appearance:none;width:26px;height:26px;margin-top:-10px;
  border-radius:var(--r-btn-round);background:var(--accent);
  border:var(--bw) solid var(--bc);box-shadow:var(--shadow);
}
.slider::-moz-range-track{
  height:9px;border-radius:5px;
  border:var(--bw-thin) solid var(--bc-soft);
  background:
    repeating-linear-gradient(90deg,transparent 0 calc(20% - 1px),var(--bc-soft) calc(20% - 1px) 20%),
    var(--track);
}
.slider::-moz-range-progress{height:9px;border-radius:5px;background:var(--dot-done)}
.slider::-moz-range-thumb{
  width:24px;height:24px;border-radius:var(--r-btn-round);background:var(--accent);
  border:var(--bw) solid var(--bc);box-shadow:var(--shadow);
}
.slider:active::-webkit-slider-thumb{transform:scale(1.08)}
/* The thumb is the control; ring that rather than the whole 340px row. */
.slider:focus-visible{outline:none}
.slider:focus-visible::-webkit-slider-thumb{outline:3px solid var(--focus);outline-offset:2px}
.slider:focus-visible::-moz-range-thumb{outline:3px solid var(--focus);outline-offset:2px}

/* ---------- generic buttons ---------- */
.btn{
  padding:14px 20px;cursor:pointer;border:var(--bw) solid var(--bc);border-radius:var(--r-btn);
  font-family:var(--font-display);font-size:16px;font-weight:700;box-shadow:var(--shadow);
  text-transform:var(--btn-case);letter-spacing:var(--btn-track);width:100%;
}
.btn:active{transform:translateY(2px)}
.btn-primary{background:var(--knew-bg);color:var(--knew-fg);box-shadow:var(--shadow),var(--knew-glow)}
.btn-ghost{background:var(--surface);color:var(--heading);margin-top:10px}
#btn-reload{margin-top:8px}

/* ---------- reset progress ---------- */
/* Fenced off below the verse list with a rule, because it is the one control
   on the screen that destroys something. It arms on the first tap rather
   than opening a dialog: a modal on a tablet is a thing to swat away, and a
   button that has visibly changed into a warning is harder to tap by
   accident than a confirm is to dismiss. */
.reset-zone{
  margin-top:28px;padding-top:20px;border-top:var(--bw-thin) solid var(--bc-soft);
}
#btn-reset{opacity:.72}
#btn-reset.is-armed{
  background:var(--again-bg);color:var(--again-fg);border-color:var(--bc);opacity:1;
}
#reset-note{margin-top:8px}

/* ---------- results ---------- */
/* Same numbers in both themes; each one celebrates in its own accent. */
.result{display:flex;flex-direction:column;align-items:center;gap:6px;width:100%}

.res-flag{
  width:176px;height:18px;border-radius:3px;margin-bottom:12px;
  background:
    conic-gradient(#fff 90deg,#10141C 90deg 180deg,#fff 180deg 270deg,#10141C 270deg);
  background-size:9px 9px;                 /* 9px cells → two full rows of checks */
  box-shadow:0 4px 12px rgba(0,0,0,.5);
}

.res-dial{position:relative;display:grid;place-items:center;line-height:0}
.res-score{
  line-height:1;
  font-family:var(--font-display);font-size:44px;font-weight:700;color:var(--heading);
  transform:skewX(var(--skew));
}
.res-score span{font-size:22px;opacity:.6}

/* Princess tucks the number into the rainbow's arch — the arch is empty
   and shaped to hold it. Racing's gauge already has a needle sweeping
   that space, so the number sits under the dial instead. */
.res-dial--arch .res-score{position:absolute;bottom:0;left:0;right:0}
.res-score--under{margin-top:12px}

.res-tier{
  margin-top:12px;padding:8px 20px;border-radius:var(--r-pill);
  background:var(--accent);color:var(--accent-ink);
  border:var(--bw) solid var(--bc);box-shadow:var(--shadow);
  font-family:var(--font-display);font-size:24px;font-weight:700;line-height:1.1;
  transform:skewX(var(--skew));letter-spacing:var(--btn-track);
}
.res-sub{
  margin-top:10px;font-size:14.5px;color:var(--text-soft);font-weight:600;
  max-width:30ch;line-height:1.5;text-align:center;
}

.res-sparkles{display:flex;gap:18px;margin-top:12px;font-size:20px;color:var(--dot-now)}
.res-sparkles span{animation:twinkle 2.4s ease-in-out infinite}
.res-sparkles span:nth-child(2){animation-delay:.5s}
.res-sparkles span:nth-child(3){animation-delay:1s}
@keyframes twinkle{0%,100%{opacity:.3;transform:scale(.75)}50%{opacity:1;transform:scale(1.15)}}

.done-actions{width:100%;max-width:320px;margin-top:22px}

/* ---------- empty state ---------- */
.empty{text-align:center;padding:34px 10px}
.empty-ico{font-size:34px;color:var(--accent-ink)}
.empty-t{font-family:var(--font-display);font-size:19px;font-weight:700;color:var(--heading);margin-top:10px}
.empty-s{font-size:13px;color:var(--text-soft);margin:6px 0 18px;line-height:1.5}
.empty .btn{max-width:260px;margin:0 auto}

/* ---------- toast ---------- */
.toast{
  position:fixed;left:50%;bottom:22px;transform:translateX(-50%);
  padding:11px 18px;border-radius:var(--r-pill);font-size:13px;font-weight:700;
  background:var(--surface);color:var(--heading);border:var(--bw) solid var(--bc);
  box-shadow:var(--shadow-lg);z-index:50;max-width:90%;text-align:center;
}

/* ---------- a11y ---------- */
button{font:inherit;color:inherit}
:focus-visible{outline:3px solid var(--focus);outline-offset:3px}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
