/* TypeDojo — lesson pages
   One stylesheet for both lesson layouts. Everything they share — the head,
   the Caps Lock warning, the stats bar and toggles, the result overlay and the
   idle pause — is written once. Only the typing surface differs, and the two
   engine blocks are fenced below:

     .type-box   a row of 8 tiles, paged
     .type-line  the whole passage on ruled rows, scrolled

   Previously this lived in two files that were 85% identical, and a change had
   to be made twice. Learning page: NO entry-load animation (start typing
   immediately); the only motion respects reduced-motion. */

/* ── Screen-reader-only helper ── */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ══════════════════════════
   HEAD — Back link (top-left) + centred lesson context above the tiles
══════════════════════════ */
.type-head__back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-dim);
  transition: color 0.18s ease;
}
.type-head__back:hover { color: #ffffff; }
.type-head__back svg { width: 18px; height: 18px; }

/* Lesson/section — centred, labels the typing area */
.type-context {
  text-align: center;
  margin-top: 26px;
}
.type-context__lesson {
  font-size: clamp(19px, 2.5vw, 26px);
  font-weight: 800;
  letter-spacing: -0.2px;
  color: #dbeeff;
}
.type-context__section {
  margin-top: 5px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
}

/* ══════════════════════════════════════════════════════════════════════
   ENGINE 1 of 2 — .type-box (tiles). Used only by typing-box-layout.html.
   ══════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════
   TYPING BOX
══════════════════════════ */
/* No card — tiles float on the dark background for a neat, clean surface */
.type-box {
  margin-top: 60px;
}

/* Row of upcoming letters — each in its own tile. The active tile (the
   letter to type NOW) is at full focus with the brand highlight; the rest
   sit at ~60% so kids instantly see what's next. */
.tile-row {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 30px;
  flex-wrap: wrap;
}

/* Page turn — the finished page floats up and out, the next page rises in. */
.tile-row.batch-out { animation: batch-out 0.18s ease-in forwards; }
.tile-row.batch-in  { animation: batch-in 0.22s ease-out; }
@keyframes batch-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-16px); }
}
@keyframes batch-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Trailing empty tiles on a short final page. */
.tile:empty { visibility: hidden; }

/* KEYCAP tiles — the row of letters. Base holds STRUCTURE only; each state
   class below owns the APPEARANCE. No shadows anywhere. */
.tile {
  position: relative;
  width: clamp(56px, 9vw, 85px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border-radius: 10px;
  border: 2px solid transparent;                 /* colour comes from the state class */
  font-family: 'Atkinson Hyperlegible Mono', 'Comic Mono', 'Cousine', monospace;
  font-size: clamp(26px, 4vw, 40px);
  font-weight: 700;
  transition: opacity 0.15s ease, border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

/* INACTIVE — upcoming letters: muted cool slate, recessive. */
.tile--inactive {
  background: rgba(255, 255, 255, 0.025);
  border-color: rgba(255, 255, 255, 0.16);
  color: #8D96AC;
  opacity: 0.6;
}

/* DONE — typed correctly: calm sage-green tint (same "green = good" language
   as the toggle "on" state). Quieter than the bold blue ACTIVE tile so it
   confirms progress without competing for focus. */
.tile--done {
  background: #1e3440;      /* dark teal fill — a defined "confirmed" tile */
  border-color: #4d8f7b;
  color: #8fd3be;
  opacity: 0.9;
}

/* Reward: a mint star pops above a tile the instant it's typed correctly,
   then fades — a fleeting "nice!" with no permanent clutter. */
.tile--done::after {
  content: "★";
  position: absolute;
  top: -26px;
  left: 50%;
  font-size: 20px;
  line-height: 1;
  color: #8fd3be;
  pointer-events: none;
  opacity: 0;                        /* resting state after the pop finishes */
  animation: star-pop 0.5s ease-out;
}
@keyframes star-pop {
  0%   { opacity: 0; transform: translate(-50%, 6px)  scale(0.4); }
  40%  { opacity: 1; transform: translate(-50%, 0)    scale(1.15); }
  100% { opacity: 0; transform: translate(-50%, -6px) scale(1); }
}

/* ACTIVE — the key to press now: solid blue fill, white letter, lighter edge. */
.tile--active {
  opacity: 1;
  border-color: #6c9ef0;   /* a step lighter than the fill — soft edge */
  color: #ffffff;
  background: #4f86e8;      /* balanced blue, aligned with the brand --blue */
}

/* Piano-key press — fires once per keystroke (JS adds the `press` class):
   the tile lifts up, then settles back down. */
.tile.press { animation: tile-press 0.4s ease-out; }
@keyframes tile-press {
  0%   { transform: translateY(0); }
  50%  { transform: translateY(-5px); }   /* up */
  100% { transform: translateY(0); }       /* come back down */
}

/* ERROR — a mistyped key: brief coral flash (JS reverts it to active after 200ms). */
.tile--error {
  opacity: 1;
  border-color: #d1667e;
  color: #ffd3dc;
  background: #3f1f28;      /* dark coral fill — clear but not glaring */
}

/* Shake on a wrong key (added/removed by the typing JS). */
.tile.shake { animation: tile-shake 0.2s ease; }
@keyframes tile-shake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-5px); }
  40%, 80% { transform: translateX(5px); }
}
@media (prefers-reduced-motion: reduce) {
  .tile.shake,
  .tile.press,
  .tile--done::after { animation: none; }   /* no motion for sensitive users */
}


/* Tiles need to tighten up before the rest of the page does. */
@media (max-width: 480px) {
  .tile-row { gap: 8px; }
}

/* ══════════════════════════════════════════════════════════════════════
   ENGINE 2 of 2 — .type-line (ruled rows). Used only by typing-line-layout.html.
   ══════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════
   TYPING LINE
   The whole passage on ruled rows. One span per character so each carries its
   own state; words are wrapped so a line break never splits one.
══════════════════════════ */
.type-line {
  --lh: 58px;                        /* row height — window, rules and scroll step */
  --rows: 4;                         /* rows visible at once */
  margin-top: 52px;
  /* Keeps the passage off the column edges. Sits on the wrapper, not on the
     text itself — padding there would shift the caret, which is positioned
     against that element. */
  padding: 0 clamp(14px, 3vw, 40px);
}

/* Fixed window onto the passage — everything past four rows is clipped, and the
   text slides up underneath as the learner advances. */
.type-line__viewport {
  height: calc(var(--lh) * var(--rows));
  overflow: hidden;
  /* The row being typed sits at full strength and everything below it steps
     back, so the eye lands on line one. This has to be a mask rather than an
     opacity on the rows: the passage is one wrapping flow, so there is no
     "line 2" element to select — and a mask stays fixed to the window while
     the text scrolls underneath it. */
  --fade: linear-gradient(to bottom,
          #000                 0,
          #000                 calc(var(--lh) * 1),   /* row 1 — full strength */
          rgba(0, 0, 0, 0.70)  calc(var(--lh) * 1),   /* rows 2-4 — one flat step */
          rgba(0, 0, 0, 0.70)  100%);
  -webkit-mask-image: var(--fade);
          mask-image: var(--fade);
}

.type-line__text {
  position: relative;                /* the caret is positioned against this */
  margin: 0;
  will-change: transform;
  /* Eased at BOTH ends, like the tile row's batch-out/batch-in pair — it drifts
     into motion and drifts out of it. A fast-start ease-out covers the same
     distance in the same time but reads as a lurch. */
  transition: transform 0.5s cubic-bezier(0.65, 0, 0.35, 1);
  font-family: "Atkinson Hyperlegible Mono", ui-monospace, monospace;
  font-size: clamp(20px, 2.8vw, 32px);
  /* 400, not 700 — at 32px the bold face reads as shouting, and the passage is
     something to read rather than a headline. */
  font-weight: 400;
  line-height: var(--lh);
  /* Enough for the active block to sit in without touching its neighbours;
     0.18em was well past that and pulled the words apart. */
  letter-spacing: -0.08em;
  color: #8D96AC;                    /* untyped — same dim as an inactive tile */
  white-space: normal;               /* breaks come from <wbr>, not from spaces */
  /* One rule under every wrapped row, drawn from the line box itself — correct
     at any width, with nothing measured in JS. */
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent calc(var(--lh) - 1px),
    var(--card-bdr) calc(var(--lh) - 1px),
    var(--card-bdr) var(--lh)
  );
}

/* A word plus its trailing space, kept whole. `pre` stops that space collapsing
   the way normal whitespace would; the <wbr> after each group is what lets the
   line break. */
.type-line__word {
  display: inline-block;
  white-space: pre;
}

/* Untyped — the tile's inactive surface and text colour, no border. Its 0.6
   opacity is deliberately not carried over: on a tile that dims 8 glanceable
   letters, but here it's the passage being read ahead, and 0.6 puts it at
   2.8:1 — below the large-text floor. */
.type-line__char {
  position: relative;
  z-index: 1;                        /* text rides above the sliding block */
  margin: 0 1px;
  border-radius: 3px;
  background: rgba(255, 255, 255, 0.025);
  color: #8D96AC;
  opacity: 0.85;                     /* softened, but still above 4:1 to read ahead */
  transition: color 0.18s ease, background 0.18s ease, opacity 0.18s ease;
}

/* Same state language as the tiles: teal confirmed, blue active, rose error —
   tinted text only, no fill, so the line stays a clean run of characters. */
/* Tinted bands rather than the tiles' solid fills — at this density a solid
   block per character would chop the passage into chunks. Done carries no
   radius so consecutive characters form one continuous highlight; errors get
   one because they're usually isolated. Stepped back to 0.8 so the run of
   completed green doesn't compete with the blue active character. */
.type-line__char--done {
  color: #8fd3be;
  background: rgba(77, 143, 123, 0.18);   /* teal, from the tile's border */
  opacity: 0.8;
}
/* The tile's #3f1f28 fill, lifted. At its tile value it measures 1.10:1 against
   the page — on a tile a border defines the edge, but as a bare band behind
   text it would be all but invisible. */
/* The block behind turns rose too — a wrong key should recolour the whole cell,
   not sit a red glyph on a blue marker. opacity is reset because the base 0.85
   would let that blue bleed through. */
.type-line__char--error {
  color: #ffd9e1;
  background: transparent;
  opacity: 1;
}
/* White on the blue block, exactly as on an active tile. The block itself is
   the sliding caret below, not a background on this span — a background here
   would be destroyed and redrawn each keystroke, which kills the slide. */
.type-line__char--active {
  color: #ffffff;
  background: transparent;           /* the sliding block supplies the fill */
  opacity: 1;
}

/* One caret for the whole passage, slid from character to character rather
   than a marker redrawn under each — that slide is what makes it read as flow
   instead of a series of jumps. */
.type-line__caret {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;                        /* sits behind the characters */
  border-radius: 5px;
  background: #4f86e8;               /* the tile's active fill */
  pointer-events: none;
  will-change: transform;
  opacity: 1;
  /* Long enough that consecutive keystrokes overlap into one continuous glide
     instead of a series of hops, on a gentle ease-out so it settles rather than
     arrives. At 0.12s with a sharper curve each move finished and stopped dead,
     which is what made it feel mechanical. */
  transition: transform 0.18s cubic-bezier(0.33, 1, 0.68, 1),
              width     0.18s cubic-bezier(0.33, 1, 0.68, 1),
              opacity   0.2s  ease;
}
/* Flashed while a wrong key is showing, then cleared back to blue. Uses the
   same lifted tile-error red as the rest of the error language. */
.type-line__caret.is-error { background: #5f2f3e; }

@media (prefers-reduced-motion: reduce) {
  .type-line__caret { transition: none; }
  .type-line__char  { transition: none; }
  .type-line__text  { transition: none; }   /* rows change without the glide */
}

@media (max-width: 560px) {
  /* Set on the wrapper, not the text — the window height keys off the same
     variable, so overriding it lower down would shrink the rows but leave a
     four-row-tall gap below them. */
  .type-line { --lh: 46px; }
}


/* ══════════════════════════════════════════════════════════════════════
   SHARED FROM HERE DOWN — both layouts use everything below.
   ══════════════════════════════════════════════════════════════════════ */

/* Caps Lock warning — a dimmed, blurred full-page overlay. The backdrop does
   the separating, so the message needs no special colour to stand out. */
.caps-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 24px;
  background: linear-gradient(160deg, #1D2540, #141A2E);   /* fully opaque — page hidden behind */
  animation: caps-fade 0.2s ease-out;
}
.caps-overlay__inner {
  max-width: 600px;
  padding: 60px 52px;
  border-radius: 28px;
  background: var(--card-bg);            /* same surface + border as the sidebar cards */
  border: 1px solid var(--card-bdr);
  text-align: center;
  animation: caps-rise 0.25s ease-out;
}
/* Blue square badge with the caps-lock arrow. */
.caps-overlay__icon {
  display: inline-grid;
  place-items: center;
  width: 104px;
  height: 104px;
  margin-bottom: 32px;
  border-radius: 22px;
  color: #E8EFF8;
  background: linear-gradient(135deg, #4A7BC8, #3E67AC);   /* brand blue, muted */
  box-shadow: 0 14px 40px rgba(62, 103, 172, 0.22);
}
.caps-overlay__icon svg {
  width: 54px;
  height: 54px;
}
.caps-overlay__title {
  margin: 0 0 14px;
  font-size: 34px;
  font-weight: 800;
  color: #C3D0E4;                    /* matches the result screen headline */
}
.caps-overlay__text {
  margin: 0;
  font-size: 19px;
  line-height: 1.6;
  color: #A7B0C5;
}
.caps-overlay[hidden] { display: none; }

@keyframes caps-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes caps-rise {
  from { opacity: 0; transform: translateY(10px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .caps-overlay,
  .caps-overlay__inner { animation: none; }
}

/* ══════════════════════════
   LESSON RESULT — full-screen overlay
   Same shell as the caps warning, but interactive: it's a real dialog that
   stays until the learner picks an action.
══════════════════════════ */
.result-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 24px;
  overflow-y: auto;                  /* short viewports scroll rather than clip */
  background: linear-gradient(160deg, #1D2540, #141A2E);
  animation: caps-fade 0.2s ease-out;
}
.result-overlay[hidden] { display: none; }

.result-overlay__inner {
  position: relative;                /* anchors the mascot */
  width: 100%;
  max-width: 760px;                  /* wider than caps — carries stats + actions */
  padding: 44px 48px 36px;
  border-radius: 28px;
  background: var(--card-bg);
  border: 1px solid var(--card-bdr);
  text-align: center;
  animation: caps-rise 0.25s ease-out;
}
/* tabindex="-1" exists purely so focus can be moved into the dialog on open.
   It's never a tab stop, so it must not paint a focus ring. */
.result-overlay__inner:focus,
.result-overlay__inner:focus-visible { outline: none; }

/* ── Verdict ── */
/* Mascot — absolutely placed, so the artwork's transparent margin costs no
   layout space and the headline stays optically centred. */
/* Sized against the artwork's own 246x329, and parked in the clear column left
   of the centred headline — the star fan starts ~180px in from the card edge. */
.result__ninja {
  position: absolute;
  left: 40px;
  top: 15px;
  /* Sized by HEIGHT, not width. The two poses have different aspect ratios
     (246x329 cheer vs 204x319 thumbs-up), so a shared width would make him
     jump 36px taller the moment the thumbs-up pose appears. */
  width: auto;
  height: 214px;
  pointer-events: none;
  user-select: none;
  /* He simply appears, then hops once after the stars have finished landing —
     so the cheer reads as a reaction to the result, not a second entrance. */
  transform-origin: 50% 100%;                         /* he compresses from his feet */
  animation: ninja-in    0.4s ease-out 0.05s backwards,
             ninja-cheer 1.5s ease-in-out 1.3s;
}
/* Arms-down packs more body into the same frame, so this pose is sized off its
   width instead — tuned by eye so the character reads the same size as the
   arms-up pose from the same top edge. */
.result__ninja--thumbs {
  width: 140px;
  height: auto;
  top: 15px;
  left: 45px;                                         /* narrower pose, nudged to re-centre */
}

/* Narrower frame again (193x321) because the raised fist sits close to the
   body. 132px puts his body at the same scale as the thumbs-up, and the extra
   4px of left keeps him centred in the same column. */
.result__ninja--encourage {
  width: 135px;
  height: auto;
  top: 15px;
  left: 50px;
}

/* A plain fade with a touch of scale — any travel here becomes a jump of its
   own, which is what made him appear to jump twice. */
@keyframes ninja-in {
  0%   { opacity: 0; transform: scale(0.94); }
  100% { opacity: 1; transform: none; }
}
/* A single celebratory hop. The squash and stretch is kept light — just enough
   to give the jump weight, not enough to read as rubber. */
@keyframes ninja-cheer {
  0%, 100% { transform: translateY(0)     scaleX(1)     scaleY(1); }
  8%       { transform: translateY(2px)   scaleX(1.02)  scaleY(0.97); }  /* crouch */
  24%      { transform: translateY(-14px) scaleX(0.985) scaleY(1.03); }  /* launch */
  38%      { transform: translateY(-16px) scaleX(1)     scaleY(1); }     /* hang */
  54%      { transform: translateY(0)     scaleX(1.03)  scaleY(0.96); }  /* land */
  66%      { transform: translateY(-4px)  scaleX(0.995) scaleY(1.01); }  /* rebound */
  78%      { transform: translateY(0)     scaleX(1)     scaleY(1); }     /* settle */
}

.result__stars {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  margin-bottom: 26px;
}
.result__defs { position: absolute; width: 0; height: 0; }

/* Fanned, not flat: the middle star is largest, and each pair steps down and
   drops away so the row arcs. Size, lift, tilt and stagger are all per-slot
   custom properties — the keyframes read them, so one animation serves all
   five without needing five sets of keyframes. */
.result__star {
  position: relative;
  display: grid;
  place-items: center;
  transform: translateY(var(--sy)) rotate(var(--sr));
}
/* --sx is the extruded depth, scaled to each star: a 4px edge on a 44px star
   is nearly 10% of it and turns muddy, while the 68px centre can carry more. */
.result__star:nth-child(1) { --ss: 44px; --sy: 15px; --sr: -14deg; --sd: 0.40s; --sx: 3px; }
.result__star:nth-child(2) { --ss: 55px; --sy:  6px; --sr:  -7deg; --sd: 0.52s; --sx: 4px; }
.result__star:nth-child(3) { --ss: 68px; --sy:  0px; --sr:   0deg; --sd: 0.64s; --sx: 5px; }
.result__star:nth-child(4) { --ss: 55px; --sy:  6px; --sr:   7deg; --sd: 0.76s; --sx: 4px; }
.result__star:nth-child(5) { --ss: 44px; --sy: 15px; --sr:  14deg; --sd: 0.88s; --sx: 3px; }

.result__star svg {
  position: relative;
  z-index: 1;
  display: block;
  width: var(--ss);
  height: var(--ss);
  fill: rgba(255, 255, 255, 0.06);        /* unearned — a quiet empty slot */
  stroke: rgba(255, 255, 255, 0.12);
  stroke-width: 1;
  stroke-linejoin: round;                 /* softened points, not needle-sharp */
}
.result__star.is-earned svg {
  fill: url(#starGrad);
  stroke: rgba(242, 206, 123, 0.4);       /* lit top edge */
  /* Depth on a dark surface has to come from a solid darker-GOLD edge, not a
     black shadow — black on navy just smudges. Zero blur makes the first
     drop-shadow trace the star's outline exactly, giving a clean extruded
     face; the second is the soft contact shadow it casts on the card. */
  filter: drop-shadow(0 var(--sx) 0 #9C7430)
          drop-shadow(0 calc(var(--sx) + 3px) 7px rgba(0, 0, 0, 0.3))
          drop-shadow(0 0 10px rgba(229, 184, 95, 0.28));
}

/* Earned stars land one at a time, with a little overshoot. */
.result__star.is-earned {
  animation: result-star-land 0.52s cubic-bezier(0.34, 1.56, 0.64, 1) var(--sd) backwards;
}

/* Warm bloom that blossoms behind the star as it lands, then clears. */
.result__star.is-earned::before {
  content: '';
  position: absolute;
  inset: -16px;
  border-radius: 50%;
  background: radial-gradient(circle,
              rgba(229, 184, 95, 0.15) 0%,
              rgba(229, 184, 95, 0) 74%);
  opacity: 0;                             /* resting state once the burst ends */
  animation: star-bloom 0.6s ease-out var(--sd) backwards;
}

/* Named for the result screen — `star-pop` is already taken by the mint star
   that fires above a correctly-typed tile. */
/* transform is one property, so the fan's lift and tilt have to be restated in
   every keyframe — otherwise the animation would flatten the arc while running.
   The vars keep it to a single rule. */
@keyframes result-star-land {
  0% {
    opacity: 0;
    transform: translateY(var(--sy)) rotate(calc(var(--sr) - 30deg)) scale(0.2);
  }
  60% {
    opacity: 1;
    transform: translateY(var(--sy)) rotate(calc(var(--sr) + 8deg)) scale(1.22);
  }
  100% {
    opacity: 1;
    transform: translateY(var(--sy)) rotate(var(--sr)) scale(1);
  }
}
@keyframes star-bloom {
  0%   { opacity: 0; transform: scale(0.6); }
  40%  { opacity: 0.7; }
  100% { opacity: 0; transform: scale(1.3); }
}
.result__title {
  margin: 0 0 8px;
  font-size: clamp(26px, 3.4vw, 34px);
  font-weight: 800;
  /* Deliberately short of white — at this size and weight, near-white glares
     against the navy. Still ~9:1, well clear of the 3:1 large-text minimum. */
  color: #C3D0E4;
}
.result__sub {
  margin: 0 0 30px;
  font-size: 16px;
  line-height: 1.6;
  color: #A7B0C5;
}

/* ── The three numbers ── */
.result__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  margin: 0 0 22px;
  padding: 22px 18px;
  border-radius: 18px;
  /* A soft inset panel rather than rules — the caps card carries exactly one
     border line, and stacking more inside cuts the surface up. */
  background: rgba(255, 255, 255, 0.035);
}
/* Hairlines between the three, not across the card — they separate the numbers
   without cutting the surface into bands. */
.result__stat { min-width: 0; }
.result__stat + .result__stat { border-left: 1px solid rgba(255, 255, 255, 0.07); }
.result__stat-label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-dim);
}
.result__stat-value {
  margin: 6px 0 0;
  font-size: clamp(26px, 3.2vw, 34px);
  font-weight: 800;
  color: #C3D0E4;                    /* same calmed tone as the headline */
  font-variant-numeric: tabular-nums;
}
.result__unit {
  margin-left: 3px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text-dim);
}

/* ── Actions — Back sits left, Redo + Continue group right ── */
.result__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 30px;
}
/* The auto margin does the splitting, so the pair needs no wrapper element. */
.result__btn--ghost { margin-right: auto; }
/* Same construction as the nav buttons in critical.css: no CSS border — the
   ring is a box-shadow — pill radius, and a lift on hover. */
.result__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 26px;
  border: none;
  border-radius: var(--r-pill);
  font-size: 15px;
  font-weight: 800;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transform: translateY(0);
  transition: background 0.22s ease, box-shadow 0.22s ease,
              color 0.22s ease, transform 0.22s ease-out;
}
.result__btn:active { opacity: 0.85; transform: translateY(0); }
/* Keyboard focus only — on-brand instead of the browser's default ring. */
.result__btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--yellow), 0 0 0 5px rgba(255, 140, 97, 0.25);
}

/* Back — quietest of the three: a soft surface at rest, ring only on hover,
   so it never competes with Redo or Continue. */
.result__btn--ghost {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-dim);
}
.result__btn--ghost:hover {
  background: rgba(255, 255, 255, 0.08);
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.18);
  color: #eef6ff;
}

/* Redo — matches .btn-login */
.result__btn--secondary {
  background: rgba(255, 255, 255, 0.07);
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.18);
  color: rgba(255, 255, 255, 0.90);
}
.result__btn--secondary:hover {
  background: rgba(255, 255, 255, 0.13);
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.36);
  color: #ffffff;
}

/* Continue — matches .btn-signup */
.result__btn--primary {
  background: var(--yellow);
  box-shadow: 0 0 0 2px var(--yellow), 0 4px 14px rgba(255, 140, 97, 0.22);
  color: #1A2238;
}
/* Keycap on Continue — always visible, so the shortcut teaches itself. A hover
   tooltip would never appear on touch, and rarely for keyboard users. */
/* Bare glyph, no keycap — the return arrow carries the meaning on its own.
   Kept at ~1.3x the 15px label: much larger and the arrow out-shouts the word. */
.result__kbd {
  display: inline-grid;
  place-items: center;
  margin-left: 9px;
  opacity: 0.75;                     /* a hint, not a second label */
}
.result__kbd svg { width: 20px; height: 20px; }

/* Less padding on the keycap side, so the label stays optically centred
   instead of being pushed left by the key. */
.result__btn--primary { padding: 0 18px 0 26px; }
.result__btn--primary:hover {
  box-shadow: 0 0 0 2px var(--yellow), 0 6px 18px rgba(255, 140, 97, 0.28);
  transform: translateY(-2px);
}

/* ── Ad — kept well clear of the buttons so no one clicks it by accident ── */
.result__ad { margin-top: 32px; }
/* Same treatment as the sidebar ad box in common.css — solid, not dashed. */
.result__ad .ad-slot__box {
  width: 100%;
  max-width: 728px;
  height: 90px;
  margin: 0 auto;
  background: var(--card-bg);
  border: 1px solid var(--card-bdr);
  border-radius: var(--r-md);
  color: var(--text-faint);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
}

/* At 160px wide he reaches x=200, and the centred star fan starts at x=227 on a
   full-width card — so he needs the card at ~706px+ to stay clear. */
@media (max-width: 780px) {
  .result__ninja { display: none; }
}

@media (max-width: 560px) {
  .result-overlay__inner { padding: 32px 22px 26px; }
  /* Scale the fan down so five stars still fit a 360px phone. */
  .result__star:nth-child(1),
  .result__star:nth-child(5) { --ss: 34px; --sy: 11px; --sx: 2px; }
  .result__star:nth-child(2),
  .result__star:nth-child(4) { --ss: 42px; --sy:  4px; --sx: 3px; }
  .result__star:nth-child(3) { --ss: 52px; --sx: 4px; }
  .result__stats { grid-template-columns: 1fr; gap: 16px; }
  .result__btn { width: 100%; }
  /* Stacked: the split collapses and Back drops below the two real actions. */
  .result__btn--ghost { margin-right: 0; order: 1; }
  .result__ad .ad-slot__box { height: 100px; }   /* 320x100 territory on phones */
}

@media (prefers-reduced-motion: reduce) {
  .result-overlay,
  .result-overlay__inner,
  .result__ninja,
  .result__star.is-earned { animation: none; }
  /* The bloom is pure celebration — drop it entirely. */
  .result__star.is-earned::before { display: none; }
}

/* ══════════════════════════
   STATS + CONTROLS BAR — WPM · Accuracy · Time  +  sound toggle · settings
   Calm, on-brand; centred below the tiles.
══════════════════════════ */
.type-bar {
  position: relative;
  overflow: hidden;                 /* clip the settings panel */
  width: 100%;
  margin: 48px 0 0;
  display: flex;
  align-items: center;
  justify-content: space-between;   /* stats · gear · toggles */
  gap: 20px;
  padding: 12px 26px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--card-bdr);
  border-radius: var(--r-pill);
  transition: background 0.25s ease, border-color 0.25s ease;
}
/* Collapsed (gear only) — drop the pill so there's no empty frame */
.type-bar.is-settings {
  background: transparent;
  border-color: transparent;
}

/* Stats (left) */
.type-bar__stats {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.type-bar__stat { display: flex; align-items: center; gap: 7px; }
.type-bar__icon { display: inline-flex; color: var(--text-dim); }
.type-bar__icon svg { width: 18px; height: 18px; }
.type-bar__label {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-dim);
}
.type-bar__value {
  font-size: 16px;
  font-weight: 800;
  color: #eef6ff;
  font-variant-numeric: tabular-nums;
}
/* Live values — reserve the widest reading each stat can reach so the bar
   doesn't resize as the numbers grow (0 → 100, 0:00 → 10:00). tabular-nums
   above keeps digit widths equal; min-width stops the box itself moving.
   Right-aligned so the % stays glued to the accuracy figure. */
.type-bar__value[data-stat],
.type-bar__value [data-stat] {
  display: inline-block;
  text-align: right;
}
[data-stat="wpm"],
[data-stat="accuracy"] { min-width: 3ch; }    /* up to 3 digits */
[data-stat="time"]     { min-width: 4.5ch; }  /* "10:00" — colon is sub-digit width */
.type-bar__divider { width: 1px; height: 20px; background: var(--card-bdr); }

/* Controls (right) */
.type-bar__controls {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-shrink: 0;
}

/* Toggle switch with On/Off label — Sound / Fingers / Keyboard */
.type-toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}
.type-toggle__input { position: absolute; opacity: 0; width: 0; height: 0; }
.type-toggle__track {
  position: relative;
  width: 56px;
  height: 24px;
  border-radius: var(--r-pill);
  background: #2D3750;                /* OFF — slate track */
  box-shadow: inset 0 0 0 1px #3E4965;   /* border via shadow — no effect on knob geometry */
  transition: background 0.18s ease, box-shadow 0.18s ease;
}
/* On/Off label sits opposite the knob */
.type-toggle__track::before {
  content: "Off";
  position: absolute;
  top: 0;
  right: 8px;
  line-height: 24px;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: #8A94AC;                     /* OFF label (bumped from #77829B for contrast) */
}
.type-toggle__knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  box-sizing: border-box;
  background: #6F7A94;            /* OFF — slate knob */
  transition: transform 0.18s ease;
}
.type-toggle__input:checked + .type-toggle__track { background: #465571; box-shadow: inset 0 0 0 1px #586985; } /* ON — lighter slate track */
.type-toggle__input:checked + .type-toggle__track::before { content: "On"; left: 10px; right: auto; color: #D5DEEF; }
.type-toggle__input:checked + .type-toggle__track .type-toggle__knob { transform: translateX(32px); background: #D9DCE1; }
.type-toggle__input:focus-visible + .type-toggle__track { box-shadow: 0 0 0 3px rgba(143, 188, 232, 0.4); }
.type-toggle__icon { width: 22px; height: 22px; color: #66728C; transition: color 0.18s ease; }
.type-toggle__input:checked ~ .type-toggle__icon { color: #70C4AD; } /* mint — icon greens when on */

/* Settings gear — centre of the row, no background; stays above the panel */
.type-gear {
  position: relative;
  z-index: 4;
  flex-shrink: 0;
  display: grid;
  place-items: center;
  background: none;
  border: none;
  color: rgba(219, 238, 255, 0.8);   /* cool + recessive so it never competes with the warm active tile */
  cursor: pointer;
  transition: transform 0.25s ease, color 0.18s ease;
}
.type-gear:hover { color: #dbeeff; transform: scale(1.1); }
.type-gear svg { width: 32px; height: 32px; transition: transform 1.2s cubic-bezier(0.22, 1, 0.36, 1); }
/* Hover: the cog turns a half-turn (zoom is on the button, turn on the icon) */
.type-gear:hover svg { transform: rotate(180deg); }

/* Gear toggles a clean/focus mode — hides the stats (left) and toggles
   (right), leaving ONLY the gear. Click again to bring them back. */
.type-bar__stats,
.type-bar__controls { transition: opacity 0.28s ease, transform 0.32s cubic-bezier(0.4, 0, 0.2, 1); }
/* slide out toward the edges as they fade (pointer-events, not visibility,
   so BOTH directions animate) */
.type-bar.is-settings .type-bar__stats { opacity: 0; pointer-events: none; transform: translateX(-20px); }
.type-bar.is-settings .type-bar__controls { opacity: 0; pointer-events: none; transform: translateX(20px); }
/* gear spins + grows as it becomes the sole focus */
.type-bar.is-settings .type-gear { color: #dbeeff; }
.type-bar.is-settings .type-gear svg { transform: rotate(180deg) scale(1.14); }
/* hovering while collapsed → another half-turn, keeps the grown size */
.type-bar.is-settings .type-gear:hover svg { transform: rotate(360deg) scale(1.14); }

@media (prefers-reduced-motion: reduce) {
  .type-bar__stats,
  .type-bar__controls { transition: opacity 0.2s ease; }
  .type-bar.is-settings .type-bar__stats,
  .type-bar.is-settings .type-bar__controls { transform: none; }
  .type-bar.is-settings .type-gear svg { transform: rotate(180deg); }
}

/* ── Responsive ── */
@media (max-width: 760px) {
  .type-bar {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 14px;
    border-radius: var(--r-lg);
    padding: 14px 18px;
  }
  .type-bar__stats { order: 1; flex-wrap: wrap; justify-content: center; }
  .type-gear { order: 2; }
  .type-bar__controls { order: 3; width: 100%; justify-content: center; flex-wrap: wrap; gap: 16px; }
  .type-bar__settings { padding: 0 20px; }
}

/* ══════════════════════════
   IDLE PAUSE — same shell as the caps warning, but interactive, so it's a
   dialog rather than a status. Deliberately calm: no red, no countdown. A
   child who stepped away hasn't done anything wrong.
══════════════════════════ */
.pause-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: 24px;
  background: linear-gradient(160deg, #1D2540, #141A2E);
  animation: caps-fade 0.2s ease-out;
}
.pause-overlay[hidden] { display: none; }

.pause-overlay__inner {
  max-width: 600px;
  padding: 52px 48px 44px;
  border-radius: 28px;
  background: var(--card-bg);
  border: 1px solid var(--card-bdr);
  text-align: center;
  animation: caps-rise 0.25s ease-out;
}
/* tabindex="-1" only so focus can move in — never a tab stop, so no ring. */
.pause-overlay__inner:focus,
.pause-overlay__inner:focus-visible { outline: none; }

/* The mascot instead of an icon — a child returning from a break meets a face
   rather than a system symbol. Thumbs-up is the calmest of the three poses;
   cheer celebrates nothing and the raised fist reads as urgency. */
.pause-overlay__ninja {
  display: block;
  width: auto;
  height: 225px;
  margin: 0 auto 20px;
}

.pause-overlay__title {
  margin: 0 0 10px;
  font-size: 30px;
  font-weight: 800;
  color: #C3D0E4;
}
.pause-overlay__text {
  max-width: 34ch;                   /* two balanced lines, not one long one */
  margin: 0 auto 28px;
  text-wrap: balance;
  font-size: 17px;
  line-height: 1.6;
  color: #A7B0C5;
}

/* Same construction as the nav buttons: no CSS border, the ring is a shadow. */
.pause-overlay__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 34px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--yellow);
  box-shadow: 0 0 0 2px var(--yellow), 0 4px 14px rgba(255, 140, 97, 0.22);
  color: #1A2238;
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
  transform: translateY(0);
  transition: box-shadow 0.22s ease, transform 0.22s ease-out;
}
.pause-overlay__btn:hover {
  box-shadow: 0 0 0 2px var(--yellow), 0 6px 18px rgba(255, 140, 97, 0.28);
  transform: translateY(-2px);
}
.pause-overlay__btn:active { opacity: 0.85; transform: translateY(0); }
.pause-overlay__btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--yellow), 0 0 0 5px rgba(255, 140, 97, 0.25);
}

@media (prefers-reduced-motion: reduce) {
  .pause-overlay,
  .pause-overlay__inner { animation: none; }
}
