/* © 2026 Typing Adventure. All rights reserved. See LICENSE.txt. */

/* ===========================================================================
   DESIGN TOKENS
   ---------------------------------------------------------------------------
   Light theme is the default and the reference design: a slate-50 ground with
   white cards, a hairline border, and a single vibrant blue accent.

   Two colour families, and the distinction matters:

     --blue / --pink / --green …   FILLS, borders, chips, the hand diagram.
                                   Never put small text directly on them.
     --*-ink                       TEXT. Darker on light surfaces; resolves back
                                   to the bright colour on the dark themes,
                                   where the card behind it is dark.

   Every value here is checked by the contrast suite in test/run-tests.js
   across all three themes. Change a token and run it.
   =========================================================================== */
:root {
  /* Surfaces */
  --bg:        #f8fafc;   /* slate-50  — page ground */
  --bg2:       #f1f5f9;   /* slate-100 — subtle depth, hover fills */
  --card:      #ffffff;
  --border:    #e2e8f0;   /* slate-200 — the hairline on every card */
  --border-strong: #cbd5e1;

  /* Text */
  --text:      #0f172a;   /* slate-900 — body copy */
  --muted:     #64748b;   /* slate-500 — metadata; 4.8:1 on white */

  /* Accent — one primary blue, not a rainbow */
  --blue:      #2563eb;   /* blue-600 */
  --blue-soft: #dbeafe;   /* blue-100 — tinted backgrounds */

  /* Supporting hues. Kept saturated on purpose: they colour-code the eight
     fingers, and that mapping has to stay instantly distinguishable. */
  --yellow:    #f59e0b;
  --pink:      #ec4899;
  --green:     #10b981;
  --purple:    #8b5cf6;
  --red:       #ef4444;

  --shadow:    rgba(15, 23, 42, 0.08);
  --shadow-md: rgba(15, 23, 42, 0.10);

  /* Finger colours. Distinct hues, deliberately not the accent palette. */
  --f-lp: #ec4899;
  --f-lr: #f59e0b;
  --f-lm: #10b981;
  --f-li: #3b82f6;
  --f-ri: #8b5cf6;
  --f-rm: #f97316;
  --f-rr: #06b6d4;
  --f-rp: #ef4444;
  --f-thumb: #94a3b8;

  /* Shape. Crisp, not pill-shaped. */
  --r-sm: 6px;
  --r:    10px;
  --r-lg: 14px;

  /* Layout. Wide enough to use a modern desktop without becoming a stretched
     ribbon of text on an ultrawide. */
  --page-max:   1440px;
  --content-max: 1200px;
  --prose-max:   680px;
}

/* Dark theme: same roles, darker surfaces. */
:root[data-theme="dark"] {
  --bg:        #0f172a;
  --bg2:       #1e293b;
  --card:      #1e293b;
  --border:    #334155;
  --border-strong: #475569;
  --text:      #f1f5f9;
  --muted:     #94a3b8;
  --blue:      #60a5fa;
  --blue-soft: #1e3a8a;
  --shadow:    rgba(0, 0, 0, 0.4);
  --shadow-md: rgba(0, 0, 0, 0.5);
}

/* High contrast: maximum separation for low-vision users. */
:root[data-theme="highcontrast"] {
  --bg:        #000000;
  --bg2:       #000000;
  --card:      #000000;
  --border:    #ffffff;
  --border-strong: #ffffff;
  --blue:      #4dd2ff;
  --blue-soft: #003347;
  --yellow:    #ffe600;
  --pink:      #ff7ab8;
  --green:     #4dff9d;
  --purple:    #c9a5ff;
  --red:       #ff6b6b;
  --text:      #ffffff;

  --muted: #a8a8a8;
  --shadow: rgba(0,0,0,0.9);
}
:root[data-theme="highcontrast"] .key,
:root[data-theme="highcontrast"] #targetDisplay,
:root[data-theme="highcontrast"] #testWords,
:root[data-theme="highcontrast"] .stage-card,
:root[data-theme="highcontrast"] .level-card { border: 2px solid var(--text); }

/* Honour reduced-motion, either from the OS or the in-app toggle. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; }
}
:root.reduce-motion *, :root.reduce-motion *::before, :root.reduce-motion *::after {
  animation-duration: .001ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: .001ms !important;
}
:root.reduce-motion .confetti-piece { display: none; }

/* Easy-read font: wide, evenly spaced letterforms help many dyslexic readers. */
:root.easy-read body,
:root.easy-read button,
:root.easy-read textarea { font-family: Verdana, Tahoma, "Trebuchet MS", sans-serif; letter-spacing: .04em; }

/* Visible keyboard focus for anyone navigating without a mouse. */
:focus-visible { outline: 3px solid var(--pink); outline-offset: 3px; }

* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0; height: 100%; width: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
               Arial, "Noto Sans", sans-serif;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow: hidden;
  user-select: none;
}
#wrap {
  position: relative;
  height: 100vh; width: 100vw;
  display: flex; align-items: center; justify-content: center;
}
.screen {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  width: 100%;
  height: 100%;
  padding: 20px;
  text-align: center;
  overflow-y: auto;
}
.screen.active { display: flex; }

/* Browse screens can outgrow the viewport. Centre them when they fit, but fall
   back to top-aligned so the first row is never clipped above the scroll area. */
#tierScreen, #stageMapScreen, #levelMapScreen, #handGuideScreen, #progressScreen {
  justify-content: flex-start;
  padding-top: 28px;
  padding-bottom: 28px;
}
#tierScreen > *, #stageMapScreen > *, #levelMapScreen > *,
#handGuideScreen > *, #progressScreen > * { flex-shrink: 0; }
#tierScreen::before, #stageMapScreen::before, #levelMapScreen::before,
#handGuideScreen::before, #progressScreen::before { content: ""; margin-top: auto; }
#tierScreen::after, #stageMapScreen::after, #levelMapScreen::after,
#handGuideScreen::after, #progressScreen::after { content: ""; margin-bottom: auto; }

h1 { font-size: clamp(28px, 4vw, 40px); font-weight: 700; letter-spacing: -0.02em;
     color: var(--text); margin: 0; }
h2 { font-size: clamp(20px, 3vw, 30px); font-weight: 700; letter-spacing: -0.015em;
     color: var(--text); margin: 0; }
p.sub { color: var(--muted); max-width: var(--prose-max); margin: 0;
        font-size: 16px; line-height: 1.6; }

button {
  font-family: inherit;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  padding: 12px 22px;
  border-radius: var(--r);
  border: none;
  cursor: pointer;
  /* --blue-ink, not --blue: white on #4f9dff is only 2.76:1, below even the
     3:1 large-text floor. Cards override this with their own background. */
  background: var(--blue-ink);
  color: white;
  box-shadow: 0 1px 2px var(--shadow);
  transition: transform 0.08s ease;
}
button:active { transform: translateY(6px); box-shadow: 0 0 0 #2f7fe0; }
button.secondary { background: var(--purple-ink); box-shadow: 0 6px 0 #4c2e91, 0 8px 16px var(--shadow); }
button.game-cta { background: var(--pink); box-shadow: 0 6px 0 #d94e7d, 0 8px 16px var(--shadow); }
button.small { font-size: 14px; padding: 8px 18px; box-shadow: 0 4px 0 #2f7fe0, 0 6px 10px var(--shadow); }
button.small.secondary { box-shadow: 0 4px 0 #7a5de0, 0 6px 10px var(--shadow); }
.btn-row { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; }
button.share-btn {
  background: var(--green); font-size: 16px; padding: 10px 22px;
  box-shadow: 0 4px 0 #2bb37e, 0 6px 12px var(--shadow);
}

/* Stage / Level maps */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 220px));
  justify-content: center;
  gap: 18px;
  /* No inner scrollbar: a nested scroll area inside a scrolling screen is
     confusing. The screen itself scrolls when the cards overflow. */
  width: 100%;
  padding: 6px;
}
.stage-card, .level-card {
  position: relative;
  background: var(--card);
  border-radius: 20px;
  padding: 14px 10px;
  box-shadow: 0 8px 0 rgba(0,0,0,0.06), 0 10px 20px var(--shadow);
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  border: 3px solid transparent;
  transition: transform 0.1s ease, border-color 0.15s;
}
.stage-card { cursor: pointer; }
.stage-card:hover, .level-card:hover { transform: translateY(-2px); border-color: var(--blue); }
.stage-emoji { font-size: 40px; }
.stage-name { font-weight: bold; font-size: 15px; }
.stars-mini { color: var(--yellow); font-size: 14px; letter-spacing: 2px; }
.best-score-mini { color: var(--pink); font-size: 12px; font-weight: bold; }

/* Four actions per lesson now (Learn + three games), so these are compact. */
.level-card-actions { display: flex; gap: 5px; width: 100%; margin-top: 3px; }
.level-action-btn {
  flex: 1;
  font-size: 12px;
  padding: 6px 3px;
  border-radius: 30px;
  box-shadow: 0 3px 0 #2f7fe0, 0 4px 8px rgba(0,0,0,0.12);
}
.level-action-btn.game-cta { box-shadow: 0 3px 0 #d94e7d, 0 4px 8px rgba(0,0,0,0.12); }

/* HUD (shared look for learn + game screens) */
#hud, #gameHud, #testHud, #g2Hud {
  position: absolute; top: 0; left: 0; right: 0;
  display: flex; justify-content: space-between; align-items: center;
  gap: 8px;
  padding: 10px 16px;
  font-size: 14px;
  font-weight: bold;
  color: var(--text);
  flex-wrap: wrap;
}
#hud .pill, #gameHud .pill, #testHud .pill, #g2Hud .pill { background: var(--card); padding: 6px 14px; border-radius: 20px; box-shadow: 0 4px 10px var(--shadow); }
#hud button.pill-btn, #gameHud button.pill-btn, #testHud button.pill-btn, #g2Hud button.pill-btn { font-size: 13px; padding: 6px 14px; box-shadow: 0 3px 0 #2f7fe0, 0 4px 8px var(--shadow); }
#hud button.pill-btn.secondary, #gameHud button.pill-btn.secondary, #testHud button.pill-btn.secondary, #g2Hud button.pill-btn.secondary { box-shadow: 0 3px 0 #7a5de0, 0 4px 8px var(--shadow); }

/* Learn / drill play screen */
#learnPlayScreen { justify-content: center; padding: 52px 18px 14px; gap: 0; }
#instructions { font-size: 18px; color: var(--purple); font-weight: bold; }
#targetDisplay {
  background: var(--card);
  border-radius: 20px;
  padding: 20px 28px;
  box-shadow: 0 1px 2px var(--shadow), 0 6px 18px var(--shadow);
  border: 1px solid var(--border);
  width: 100%;
  max-width: 1040px;
  /* Height comes from the inner clipper, so the box is always an exact number
     of lines tall no matter what the padding is. */
  height: auto;
  /* Without this the box is a flex item that collapses to its padding, leaving
     a single sliver of text visible instead of the full multi-line block. */
  flex: 0 0 auto;
  display: flex; align-items: flex-start; justify-content: flex-start;
}

/* THE clipper. overflow:hidden on #targetDisplay clipped at the padding box,
   which is 2 x 26px taller than the content box — so a fourth line was always
   half-visible along the bottom edge. That is the "half-empty last row" bug
   returning by a different route. This element has no padding, so its height
   IS the visible area: exactly --flow-lines lines, never part of one. */
.flow-clip {
  width: 100%;
  height: calc(var(--flow-line) * var(--flow-lines));
  overflow: hidden;
}
/* Flowing multi-line text: several lines of upcoming words visible at once. */
.flow-text {
  text-align: left;
  font-size: var(--flow-size);
  line-height: var(--flow-line);
  letter-spacing: 1px;
  width: 100%;
  transition: transform .18s ease;
  will-change: transform;
}
.flow-word { display: inline-block; }
.flow-word.done { opacity: .5; }
.flow-word.active { background: rgba(255,209,102,.16); border-radius: 6px; }

/* The gap reads as a normal space while you're mid-word, and only becomes a
   visible target at the moment space is actually the next key. Showing a dot in
   every gap made the text look like it was full of full-stops. */
.flow-space {
  display: inline-block;
  width: .62em; text-align: center;
  color: transparent;          /* never render a glyph - a dot in every gap read as punctuation */
  border-radius: 5px;
}
/* When space is the next key it becomes a highlighted block, not a character. */
.flow-space.current {
  background: var(--yellow);
  color: transparent;
  animation: pulse .9s ease-in-out infinite;
}
/* Flashed when a letter is pressed but space is what's actually needed. */
.flow-space.nudge { animation: spaceNudge .45s ease; }
@keyframes spaceNudge {
  0%, 100% { background: var(--yellow); transform: scale(1); }
  50%      { background: var(--pink);   transform: scale(1.5); }
}

/* Progress bar replaces per-item dots, which became unreadable on long lessons. */
.flow-progress { width: min(560px, 80vw); height: 8px; border-radius: 5px; background: rgba(128,128,128,.22); overflow: hidden; }
.flow-progress-fill { height: 100%; background: var(--green); border-radius: 5px; transition: width .2s ease; }
.flow-progress-label { font-size: 11px; color: var(--muted); margin-top: 3px; }
.char { padding: 0 1px; border-radius: 5px; font-weight: bold; transition: color .12s, background .12s; }
.char.untyped { color: var(--muted); }
.char.current { background: var(--yellow); color: var(--text); animation: pulse 0.9s ease-in-out infinite; display: inline-block; border-radius: 5px; }
.char.typed-correct { color: var(--green); }
/* Mistakes are marked rather than blocking: you type on and can fix with Backspace. */
.char.typed-wrong { color: #fff; background: var(--red); border-radius: 5px; }
.char.extra { color: #fff; background: var(--red); opacity: .65; border-radius: 5px; }
.char.cursor { box-shadow: inset 2px 0 0 var(--blue); }
.char.space-char {
  padding: 0; border-radius: 0;
  background: transparent !important;
  border: none;
  animation: none !important;
}
.shift-hint { font-size: 14px; color: var(--pink); margin-top: -6px; }

@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.12); } }
@keyframes shake {
  10%, 90% { transform: translateX(-3px); }
  20%, 80% { transform: translateX(5px); }
  30%, 50%, 70% { transform: translateX(-8px); }
  40%, 60% { transform: translateX(8px); }
}
.shake { animation: shake 0.4s; }

#progressDots { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; max-width: min(900px, 90vw); }
.dot { width: 12px; height: 12px; border-radius: 50%; background: var(--muted); }
.dot.done { background: var(--green); }
.dot.current { background: var(--yellow); box-shadow: 0 0 0 4px rgba(255,209,102,0.4); }

#feedback { font-size: 20px; font-weight: bold; height: 26px; color: var(--green); }

/* On-screen keyboard (shared, relocated between screens) */
#keyboardArea { display: flex; flex-direction: column; align-items: center; gap: 10px; }
#keyboard { display: flex; flex-direction: column; gap: var(--key-gap); align-items: center; }
.kb-row { display: flex; gap: var(--key-gap); }
.key {
  position: relative;
  background: var(--card);
  border-radius: 10px;
  min-width: var(--key-size); height: var(--key-size);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--key-font); font-weight: bold;
  box-shadow: 0 4px 0 rgba(0,0,0,0.08);
  color: var(--text);
}
.key.bump { border-bottom: 4px solid var(--blue); }
.key.active { background: var(--yellow); box-shadow: 0 0 0 5px rgba(255,209,102,0.5), 0 4px 0 rgba(0,0,0,0.08); animation: pulse 0.6s ease-in-out infinite; }
.key.shiftkey { min-width: calc(var(--key-size) * 1.8); font-size: calc(var(--key-font) * .76); }
.key.shiftkey.active { background: var(--pink); box-shadow: 0 0 0 5px rgba(255,107,157,0.5); }
.key.spacekey { min-width: calc(var(--key-size) * 7); }

/* A small colored dot on each key shows which finger touch-typing uses for it. */
.key[data-finger]::after {
  content: ""; position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%);
  width: 6px; height: 6px; border-radius: 50%;
}
.key[data-finger="lp"]::after { background: var(--f-lp); }
.key[data-finger="lr"]::after { background: var(--f-lr); }
.key[data-finger="lm"]::after { background: var(--f-lm); }
.key[data-finger="li"]::after { background: var(--f-li); }
.key[data-finger="ri"]::after { background: var(--f-ri); }
.key[data-finger="rm"]::after { background: var(--f-rm); }
.key[data-finger="rr"]::after { background: var(--f-rr); }
.key[data-finger="rp"]::after { background: var(--f-rp); }
.key[data-finger="thumb"]::after { background: var(--f-thumb); }

/* Finger guide: prominent banner + large hand diagram highlighting the finger */
#fingerGuide { display: flex; flex-direction: column; align-items: center; gap: 6px; }
#fingerBanner {
  display: flex; align-items: center; gap: 10px;
  background: var(--card); border-radius: 30px;
  padding: 7px 18px; box-shadow: 0 4px 12px var(--shadow);
  border: 3px solid var(--muted);
  min-height: 42px;
}
#fingerHandIcon { font-size: 22px; line-height: 1; }
#fingerLabel { font-size: 17px; font-weight: bold; color: var(--text); }
#fingerKeyChip {
  font-size: 17px; font-weight: bold; min-width: 34px; text-align: center;
  padding: 3px 10px; border-radius: 8px;
  background: var(--muted); color: #fff;
}
/* Banner border + chip take the finger's own colour, tying the banner, the
   hand diagram and the highlighted key together at a glance. */
#fingerBanner.f-lp { border-color: var(--f-lp); } #fingerBanner.f-lp #fingerKeyChip { background: var(--f-lp); }
#fingerBanner.f-lr { border-color: var(--f-lr); } #fingerBanner.f-lr #fingerKeyChip { background: var(--f-lr); color: #2b2d42; }
#fingerBanner.f-lm { border-color: var(--f-lm); } #fingerBanner.f-lm #fingerKeyChip { background: var(--f-lm); color: #12203a; }
#fingerBanner.f-li { border-color: var(--f-li); } #fingerBanner.f-li #fingerKeyChip { background: var(--f-li); }
#fingerBanner.f-ri { border-color: var(--f-ri); } #fingerBanner.f-ri #fingerKeyChip { background: var(--f-ri); }
#fingerBanner.f-rm { border-color: var(--f-rm); } #fingerBanner.f-rm #fingerKeyChip { background: var(--f-rm); }
#fingerBanner.f-rr { border-color: var(--f-rr); } #fingerBanner.f-rr #fingerKeyChip { background: var(--f-rr); color: #12203a; }
#fingerBanner.f-rp { border-color: var(--f-rp); } #fingerBanner.f-rp #fingerKeyChip { background: var(--f-rp); }
#fingerBanner.f-thumb { border-color: var(--blue); } #fingerBanner.f-thumb #fingerKeyChip { background: var(--blue); }

.hands-bars { width: min(560px, 88vw); height: auto; }
.hands-bars .finger-name {
  font-family: inherit; font-size: 15px; font-weight: bold;
  fill: #8fa0bd; text-anchor: middle; opacity: .8;
}
.hands-bars .finger-name.active { fill: var(--text); opacity: 1; font-size: 17px; }
.hands-bars .palm { fill: #e4ecfb; }
.hands-bars .spacebar { fill: #dce8ff; }
.hands-bars .finger { fill: #d7e2f7; transition: fill .15s, transform .15s; transform-origin: center bottom; }
.hands-bars .finger[data-finger="lp"].active { fill: var(--f-lp); }
.hands-bars .finger[data-finger="lr"].active { fill: var(--f-lr); }
.hands-bars .finger[data-finger="lm"].active { fill: var(--f-lm); }
.hands-bars .finger[data-finger="li"].active { fill: var(--f-li); }
.hands-bars .finger[data-finger="ri"].active { fill: var(--f-ri); }
.hands-bars .finger[data-finger="rm"].active { fill: var(--f-rm); }
.hands-bars .finger[data-finger="rr"].active { fill: var(--f-rr); }
.hands-bars .finger[data-finger="rp"].active { fill: var(--f-rp); }
.hands-bars .finger[data-finger="thumb"].active { fill: var(--f-thumb); }
.hands-bars .finger.active { transform: scaleY(1.08); }

/* Confetti */
#confettiLayer { position: fixed; inset: 0; pointer-events: none; overflow: hidden; z-index: 50; }
.confetti-piece { position: absolute; top: -20px; width: 10px; height: 10px; border-radius: 2px; animation: fall linear forwards; }
@keyframes fall { to { transform: translateY(110vh) rotate(360deg); opacity: 0.9; } }

.stars-big { font-size: 40px; letter-spacing: 8px; color: var(--yellow); }

/* Device notice (shown only on touch-primary devices without a physical keyboard) */
#deviceNotice {
  position: fixed; top: 0; left: 0; right: 0; z-index: 70;
  background: var(--purple); color: white;
  font-size: 14px; font-weight: bold;
  padding: 10px 44px 10px 16px;
  text-align: center;
  box-shadow: 0 4px 10px var(--shadow);
}
#deviceNotice.hidden { display: none; }
#deviceNoticeClose {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  background: transparent; box-shadow: none; padding: 4px 8px; font-size: 16px;
}
#deviceNoticeClose:active { transform: translateY(-50%) translateY(2px); }

/* Global mute button */



/* Spoken-guidance toggle, sat beside the mute button. It lives here rather than
   only in Settings because a feature aimed at young children has to be findable
   without reading a settings page. Dimmed when off. */

.speech-btn.on { opacity: 1; filter: none; box-shadow: 0 0 0 3px rgba(155,123,255,.45), 0 4px 10px var(--shadow); }
.speech-btn:active { transform: translateY(2px); }
.speech-btn.unsupported { display: none; }

.home-stat { opacity: 1; }
.home-stat strong { color: var(--blue); font-size: 17px; }
button.linklike {
  background: none; box-shadow: none; color: var(--muted);
  font-size: 12px; padding: 4px 8px; text-decoration: underline;
}
button.linklike:hover { color: var(--pink); }
button.linklike:active { transform: none; box-shadow: none; }

/* ===== Touch / mobile ===== */
:root.touch-device .key {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  user-select: none;
}
:root.touch-device .key.pressed { transform: translateY(3px); filter: brightness(.92); }
:root.touch-device .key[data-shift-indicator="1"].held {
  background: var(--pink); color: #fff; box-shadow: 0 0 0 4px rgba(255,107,157,.45);
}
.key.backspacekey { min-width: 150px; font-size: 13px; background: var(--purple); color: #fff; }

/* ===== Extra game modes ===== */
#g2PlayScreen { justify-content: space-between; padding: 70px 20px 20px; gap: 8px; }
#g2FieldWrap { position: relative; flex: 0 0 auto; }
#g2Field {
  position: relative; overflow: hidden;
  width: min(var(--content-max), calc(100vw - 96px)); height: clamp(220px, 34vh, 330px);
  background: linear-gradient(180deg, #eaf6ff 0%, #ffffff 100%);
  border: 3px solid #dce8ff; border-radius: 20px;
  box-shadow: 0 10px 0 rgba(0,0,0,.06), 0 14px 28px var(--shadow);
}
:root[data-theme="dark"] #g2Field { background: linear-gradient(180deg, #1b2340 0%, #222a44 100%); border-color: #2c3557; }
#g2Countdown {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,.85); font-size: 76px; font-weight: bold; color: var(--pink); z-index: 5;
}
#g2Countdown.hidden { display: none; }

/* Balloon Pop */
.balloon { position: absolute; display: flex; flex-direction: column; align-items: center; will-change: top; }
.balloon-body {
  width: 62px; height: 74px; border-radius: 50% 50% 46% 46%;
  box-shadow: inset -6px -8px 12px rgba(0,0,0,.12);
  position: relative;
}
.balloon-body::after {
  content: ""; position: absolute; bottom: -18px; left: 50%; width: 2px; height: 18px;
  background: rgba(0,0,0,.25); transform: translateX(-50%);
}
.balloon-text {
  margin-top: 22px; background: var(--card); border-radius: 10px; padding: 3px 9px;
  font-size: 16px; font-weight: bold; white-space: nowrap;
  box-shadow: 0 3px 8px var(--shadow);
}
.balloon.pop { animation: balloonPop .26s ease forwards; }
@keyframes balloonPop { to { transform: scale(1.7); opacity: 0; } }

/* Word Race */
.race-track { padding: 18px 14px 6px; display: flex; flex-direction: column; gap: 14px; }
.race-lane {
  position: relative; height: 42px; border-radius: 10px;
  background: repeating-linear-gradient(90deg, rgba(128,128,128,.14) 0 18px, transparent 18px 36px);
  display: flex; align-items: center;
}
.racer { position: absolute; left: 0; font-size: 30px; transition: left .25s ease; }
.lane-label { position: absolute; right: 8px; font-size: 11px; color: var(--muted); font-weight: bold; }
.race-words {
  padding: 6px 16px 14px; font-size: 1.4rem; line-height: 1.9; text-align: left;
}
.race-word { display: inline-block; margin-right: 10px; }
.race-word.done { opacity: .45; }
.race-word.active { background: rgba(255,209,102,.18); border-radius: 6px; }

/* ===== First-run onboarding ===== */
#welcomeMascot { font-size: 66px; line-height: 1; }
.welcome-choices {
  display: grid; gap: 14px; align-content: start;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
/* Resume is the primary action for a returning learner, so it gets the full
   width of the grid rather than sharing a row. */
.welcome-choice.resume { grid-column: 1 / -1; }
.welcome-choice {
  display: flex; align-items: center; gap: 14px; text-align: left;
  background: var(--card); border-radius: var(--r-lg); padding: 16px 20px;
  box-shadow: 0 1px 2px var(--shadow), 0 4px 12px var(--shadow);
  cursor: pointer; border: 1px solid var(--border);
  transition: transform .12s ease, border-color .15s, box-shadow .15s;
}
.welcome-choice:hover { transform: translateY(-2px); border-color: var(--blue);
                        box-shadow: 0 4px 6px var(--shadow), 0 12px 24px var(--shadow-md); }
/* Resume is the primary action for a returning learner. */
.welcome-choice.resume { border-color: var(--green); background: linear-gradient(90deg, rgba(61,220,151,.12), var(--card) 60%); }
.welcome-choice.resume .wc-text strong { color: var(--green-ink); }
#welcomeStats { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center;
  background: var(--card); border-radius: 30px; padding: 7px 18px; font-size: 13px;
  box-shadow: 0 4px 12px var(--shadow); }
.wc-emoji { font-size: 32px; line-height: 1; }
.wc-text { display: flex; flex-direction: column; gap: 2px; }
.wc-text strong { font-size: 17px; color: var(--blue); }
.wc-text small { font-size: 12.5px; color: var(--text); opacity: .7; }

/* ===== Difficulty tiers ===== */
.tier-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 250px));
  justify-content: center;
  gap: 16px;
  width: 100%;
  padding: 6px;
}
.tier-card {
  background: var(--card); border-radius: var(--r-lg); padding: 20px 18px;
  box-shadow: 0 1px 2px var(--shadow), 0 4px 12px var(--shadow);
  cursor: pointer; border: 1px solid var(--border);
  transition: transform .12s ease, border-color .15s, box-shadow .15s;
  display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center;
}
.tier-card:hover { transform: translateY(-2px); border-color: var(--blue);
                   box-shadow: 0 4px 6px var(--shadow), 0 12px 24px var(--shadow-md); }
.tier-card.complete { border-color: var(--green); }
.tier-emoji { font-size: 38px; line-height: 1; }
.tier-name { font-size: 20px; font-weight: bold; color: var(--blue); }
.tier-blurb { font-size: 13px; font-weight: bold; color: var(--purple); }
.tier-detail { font-size: 12px; color: var(--text); opacity: .7; line-height: 1.4; }
.tier-meta { font-size: 11px; color: var(--muted); margin-top: auto; padding-top: 4px; }
.tier-card .stage-progress { width: 100%; }

.stage-num { font-size: 11px; font-weight: bold; color: var(--muted); letter-spacing: 1px; }

/* ===== Hand position guide ===== */
#handGuideBoard { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.hg-row { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.hg-key {
  position: relative;
  width: 92px; min-height: 84px; border-radius: 14px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  color: #000; font-weight: bold;
  box-shadow: 0 5px 0 rgba(0,0,0,.12);
}
.hg-letter { font-size: 30px; line-height: 1; text-transform: uppercase; }
.hg-finger { font-size: 10.5px; }
.hg-key.f-lp { background: var(--f-lp); } .hg-key.f-lr { background: var(--f-lr); }
.hg-key.f-lm { background: var(--f-lm); } .hg-key.f-li { background: var(--f-li); }
.hg-key.f-ri { background: var(--f-ri); } .hg-key.f-rm { background: var(--f-rm); }
.hg-key.f-rr { background: var(--f-rr); } .hg-key.f-rp { background: var(--f-rp); }
.hg-key.hg-bump { outline: 4px solid var(--text); outline-offset: 2px; }
.hg-bumpdot {
  position: absolute; bottom: 7px; left: 50%; transform: translateX(-50%);
  width: 22px; height: 4px; border-radius: 3px; background: rgba(0,0,0,.55);
}
.hg-space {
  background: var(--card); border-radius: 12px; padding: 8px 40px;
  font-size: 13px; font-weight: bold; color: var(--muted);
  box-shadow: 0 4px 0 rgba(0,0,0,.08);
}
/* Sized by #handsOnKb below. Kept only as a floor for any other svg that
   might land in this container. */
#handGuideHands svg { height: auto; }
.hand-tips { display: flex; flex-direction: column; gap: 5px; max-width: 620px; }
.hand-tip { font-size: 13px; color: var(--text); opacity: .85; text-align: left; }
.hand-tip span { margin-right: 6px; }

/* ===== Continue / world progress ===== */
.stage-progress-label { font-size: 11px; color: var(--muted); }
.stage-progress { width: 100%; height: 5px; border-radius: 4px; background: rgba(128,128,128,.22); overflow: hidden; }
.stage-progress-fill { height: 100%; background: var(--green); border-radius: 4px; }

.strip-flame { font-size: 18px; }
.strip-sep { color: var(--muted); }

.streak-box {
  background: var(--card); border-radius: 18px; padding: 16px 22px;
  box-shadow: 0 6px 16px var(--shadow);
  width: min(660px, 94vw);
  display: flex; flex-direction: column; gap: 10px;
}
.streak-top { display: flex; align-items: center; gap: 14px; }
.streak-flame { font-size: 38px; filter: grayscale(1); opacity: .45; }
.streak-flame.lit { filter: none; opacity: 1; }
.streak-num { font-size: 24px; font-weight: bold; color: var(--pink); line-height: 1.1; }
.streak-sub { font-size: 12px; color: var(--muted); }
.goal-label { font-size: 13px; margin-bottom: 4px; }
.goal-bar { height: 10px; border-radius: 6px; background: rgba(128,128,128,.22); overflow: hidden; }
.goal-fill { height: 100%; background: var(--green); border-radius: 6px; transition: width .3s ease; }
.cal-row { display: flex; gap: 4px; }
.cal-day { flex: 1; height: 20px; border-radius: 4px; background: rgba(128,128,128,.2); }
.cal-day.on { background: var(--green); }
.cal-caption { font-size: 11px; color: var(--muted); }

/* ===== WPM chart ===== */
.chart-box {
  background: var(--card); border-radius: 18px; padding: 14px 18px;
  box-shadow: 0 6px 16px var(--shadow); width: min(660px, 94vw);
}
.chart-head { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; flex-wrap: wrap; gap: 6px; }
.chart-title { font-size: 13px; font-weight: bold; color: var(--purple); }
.chart-trend { font-size: 13px; font-weight: bold; color: var(--muted); }
.chart-trend.up { color: var(--green); }
.chart-trend.down { color: var(--red); }
.chart-empty { font-size: 13px; color: var(--muted); padding: 26px 0; }
.wpm-chart { width: 100%; height: 190px; display: block; overflow: visible; }
.wpm-chart .grid { stroke: rgba(128,128,128,.28); stroke-width: 1; }
.wpm-chart .axis { fill: var(--muted); font-size: 11px; font-family: inherit; }
.wpm-chart .line { fill: none; stroke: var(--blue); stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.wpm-chart .area { fill: var(--blue); opacity: .13; }
.wpm-chart .dot-pt { fill: var(--blue); }

/* ===== Key heatmap ===== */
.heat-title { font-size: 13px; font-weight: bold; color: var(--purple); }
.heat-box { display: flex; flex-direction: column; gap: 4px; align-items: center; }
.heat-row { display: flex; gap: 4px; }
.heat-key {
  width: 40px; height: 36px; border-radius: 7px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  font-weight: bold; color: #12203a;
}
.heat-ch { font-size: 13px; line-height: 1; }
.heat-val { font-size: 9px; line-height: 1.3; }
.heat-key.great { background: #3ddc97; }
.heat-key.ok    { background: #a8e77d; }
.heat-key.warn  { background: #ffd166; }
.heat-key.bad   { background: #ff8a8a; }
.heat-key.none  { background: rgba(128,128,128,.22); color: var(--muted); }
.heat-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; font-size: 11px; color: var(--muted); margin-top: 6px; }
.heat-legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; margin-right: 4px; vertical-align: -1px; }
.sw.great { background: #3ddc97; } .sw.ok { background: #a8e77d; }
.sw.warn { background: #ffd166; } .sw.bad { background: #ff8a8a; }
.sw.none { background: rgba(128,128,128,.22); }

/* ===== Certificate ===== */
.cert-controls { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.cert-controls input {
  font-family: inherit; font-size: 15px; padding: 10px 16px; border-radius: 30px;
  border: 2px solid var(--muted); background: var(--card); color: var(--text);
}
.cert-controls input:focus { outline: none; border-color: var(--blue); }
#certificate { width: min(680px, 94vw); }
.cert-border {
  background: #fffdf6; color: #2b2d42;
  border: 8px double #c9a227; border-radius: 8px;
  padding: 26px 30px; text-align: center;
  box-shadow: 0 10px 26px var(--shadow);
}
.cert-seal { font-size: 42px; }
.cert-head { font-size: 26px; font-weight: bold; color: #b8860b; letter-spacing: 1px; }
.cert-sub { font-size: 13px; color: #6b6b6b; margin: 8px 0 2px; }
.cert-name { font-size: 30px; font-weight: bold; color: #2b2d42; border-bottom: 2px solid #c9a227; display: inline-block; padding: 0 20px 4px; margin: 4px 0 6px; }
.cert-stat { font-size: 40px; font-weight: bold; color: #4f9dff; line-height: 1.1; margin: 6px 0; }
.cert-stat small { font-size: 13px; color: #6b6b6b; font-weight: normal; display: block; }
.cert-row { display: flex; justify-content: center; gap: 46px; margin: 14px 0 6px; font-size: 15px; }
.cert-row small { color: #6b6b6b; font-size: 11px; }
.cert-foot { display: flex; justify-content: space-between; font-size: 11px; color: #6b6b6b; margin-top: 18px; border-top: 1px solid #ddd; padding-top: 8px; }

@media print {
  body, html { background: #fff !important; overflow: visible !important; height: auto !important; }
  #wrap { height: auto !important; display: block !important; }
  .screen { display: none !important; }
  .screen#certScreen { display: block !important; height: auto !important; padding: 0 !important; overflow: visible !important; }
  .no-print, #hud, #gameHud, #testHud, .app-header, #copyrightFooter, #deviceNotice { display: none !important; }
  #certificate { width: 100% !important; }
  .cert-border { box-shadow: none; }
}

/* ===== Settings & custom text ===== */
.settings-group { display: flex; flex-direction: column; align-items: center; gap: 5px; }
.settings-label { font-size: 15px; font-weight: bold; color: var(--purple); }
.settings-options { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.settings-hint { font-size: 12px; color: var(--muted); }
button.opt-btn {
  background: var(--card); color: var(--text);
  font-size: 13px; padding: 7px 14px; border-radius: 30px;
  box-shadow: 0 3px 0 rgba(0,0,0,0.10);
  border: 2px solid transparent;
}
button.opt-btn.selected { border-color: var(--blue); color: var(--blue); }
button.opt-btn:active { transform: translateY(3px); box-shadow: none; }

#customInput {
  width: min(760px, 92vw); max-width: 100%;
  font-family: inherit; font-size: 16px; line-height: 1.5;
  padding: 16px; border-radius: 16px;
  border: 2px solid var(--muted); background: var(--card); color: var(--text);
  resize: vertical;
}
#customInput:focus { outline: none; border-color: var(--blue); }
.form-error { color: var(--red); font-size: 14px; font-weight: bold; min-height: 18px; }

/* ===== Weak-key report ===== */
.weak-keys { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.weak-title { font-size: 13px; font-weight: bold; color: var(--purple); }
.weak-row { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.weak-key {
  background: var(--card); border-radius: 10px; padding: 6px 10px;
  box-shadow: 0 3px 0 rgba(0,0,0,0.07);
  display: flex; flex-direction: column; align-items: center; min-width: 46px;
}
.weak-char { font-size: 17px; font-weight: bold; color: var(--red); }
.weak-pct { font-size: 11px; color: var(--muted); }
.weak-empty { font-size: 13px; color: var(--green); font-weight: bold; }

/* ===== Typing test ===== */
#testScreen { justify-content: space-between; padding: 74px 20px 24px; gap: 10px; }
#testWords {
  background: var(--card);
  border-radius: 24px;
  padding: 28px 34px;
  box-shadow: 0 10px 0 rgba(0,0,0,0.06), 0 14px 28px var(--shadow);
  width: min(940px, 94vw);
  max-height: 34vh;
  overflow: hidden;
  font-size: 1.55rem;
  line-height: 1.9;
  text-align: left;
}
.test-word { display: inline-block; margin-right: 2px; }
.test-word.done { opacity: .45; }
.test-word.active { background: rgba(255,209,102,0.18); border-radius: 6px; }

.result-hero { display: flex; flex-direction: column; align-items: center; }
.result-wpm { font-size: clamp(64px, 12vw, 110px); font-weight: bold; color: var(--blue); line-height: 1; }
.result-wpm-label { font-size: 14px; color: var(--muted); letter-spacing: 2px; text-transform: uppercase; }
.result-rating { font-size: 17px; font-weight: bold; color: var(--purple); }
.result-badge { font-size: 14px; font-weight: bold; color: var(--pink); }
.result-meta { font-size: 13px; opacity: .6; }

/* Shorter laptop screens (e.g. 1366x768): trim the keyboard block so the
   play area still fits without the page scrolling. */
@media (max-height: 820px) {

  /* The start screen gained a Settings row (5 choices). At 720px tall that
     overflowed by ~35px and produced a scrollbar, so tighten it here. */
  #welcomeScreen { gap: 10px; padding: 14px 20px; }
  #welcomeMascot { font-size: 46px; }
  .welcome-choices { gap: 10px; }
  .welcome-choice { padding: 11px 18px; gap: 12px; }
  .wc-emoji { font-size: 27px; }
  .wc-text strong { font-size: 16px; }
  .wc-text small { font-size: 12px; }

  /* Hand guide: the illustration is the point of this screen, so everything
     around it gives up room first. */
  #handGuideScreen { gap: 10px; padding: 12px 20px; }
  #handGuideScreen h2 { font-size: 26px; }
  #handGuideBoard { gap: 5px; }
  .hg-key { padding: 8px 10px; min-width: 74px; }
  .hg-letter { font-size: 22px; }
  .hg-finger { font-size: 10px; }
  .hg-space { padding: 5px 14px; font-size: 12px; }
  .hand-tips { gap: 3px; }
  .hand-tip { font-size: 12.5px; }

  /* Settings stacks six option groups; on a 720px-tall laptop that spilled
     ~48px past the fold. Tighten the rows rather than let it scroll. */
  #settingsScreen { gap: 7px; padding: 12px 20px; }
  #settingsScreen h2 { font-size: 26px; }
  .settings-group { gap: 3px; }
  .settings-label { font-size: 14px; }
  .settings-hint { font-size: 11px; line-height: 1.3; }
  button.opt-btn { font-size: 12.5px; padding: 5px 12px; }

  /* Progress screen stacks a lot of panels; shrink them to fit short laptops. */
  #progressScreen { gap: 8px; padding: 12px 20px; }
  .streak-box { padding: 10px 16px; gap: 6px; }
  .streak-flame { font-size: 28px; }
  .streak-num { font-size: 19px; }
  .cal-day { height: 14px; }
  .chart-box { padding: 8px 14px; }
  .wpm-chart { height: 130px; }
  .heat-key { width: 32px; height: 28px; }
  .heat-ch { font-size: 11px; }
  .heat-val { font-size: 8px; }

  .key { min-width: 40px; height: 40px; font-size: 15px; }
  .key.shiftkey { min-width: 70px; font-size: 12px; }
  .key.spacekey { min-width: 270px; }
  #keyboard { gap: 6px; }
  .kb-row { gap: 6px; }
  #keyboardArea { gap: 6px; }
  .hands-bars { width: min(360px, 62vw); }
  #fingerBanner { padding: 4px 12px; min-height: 32px; }
  #fingerLabel { font-size: 14px; }
  #fingerKeyChip { font-size: 14px; padding: 2px 8px; }
  #fingerGuide { gap: 2px; }
  #learnPlayScreen { gap: 4px; padding: 62px 20px 12px; }
  #feedback { font-size: 15px; height: 18px; }
  #instructions { font-size: 15px; }
  .flow-progress { height: 6px; }
  .flow-progress-label { font-size: 10px; margin-top: 1px; }
  /* Short laptops: smaller type and tighter leading, still three full lines. */
  /* Exactly three lines: 3 x 42px + 2 x 14px padding. */
  #targetDisplay { padding: 12px 22px; }
  /* Token, not a direct line-height: .flow-clip sizes itself from this, and
     the two must never disagree — see the .flow-clip comment. */
  :root { --flow-line: 42px; --flow-size: 1.35rem; }
  #testWords { max-height: 28vh; padding: 22px 30px; }
}

/* Copyright footer */
#copyrightFooter {
  position: fixed; bottom: 4px; left: 0; right: 0; z-index: 40;
  text-align: center; font-size: 11px; color: var(--muted);
  pointer-events: none;          /* never steal clicks from the app behind it */
}
/* ...but the links inside it still have to be clickable. */
#copyrightFooter a { pointer-events: auto; color: var(--blue); }
/* It is fixed to the bottom of the viewport, so on play screens it landed on
   top of the SPACE key. Play screens fill the window by design and have no
   spare room, so the footer is hidden there and shown on browse screens. */
#copyrightFooter.hidden { display: none; }

/* Game mode */
#gamePlayScreen { justify-content: space-between; padding: 74px 20px 40px; }
#gameHud .lives { letter-spacing: 2px; }
#gameField {
  position: relative;
  width: min(1000px, 94vw);
  height: clamp(230px, 38vh, 340px);
  background: linear-gradient(180deg, #ffffff 0%, #f2f7ff 100%);
  border-radius: 20px;
  box-shadow: 0 10px 0 rgba(0,0,0,0.06), 0 14px 28px var(--shadow);
  overflow: hidden;
  border: 3px solid #dce8ff;
}
#gameField::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 34px;
  height: 2px; background: repeating-linear-gradient(90deg, var(--pink) 0 10px, transparent 10px 20px);
  opacity: 0.6;
}
.falling-item {
  position: absolute;
  top: -40px;
  transform: translateX(-50%);
  background: var(--card);
  border-radius: 14px;
  padding: 8px 14px;
  font-weight: bold;
  font-size: 22px;
  box-shadow: 0 6px 14px var(--shadow);
  max-width: min(70%, 480px);
  white-space: normal;
  text-align: center;
  line-height: 1.4;
}
.falling-item.short { white-space: nowrap; max-width: none; }
/* Keep each word intact so wrapping only ever happens at spaces. */
.game-word { display: inline-block; white-space: nowrap; }
/* .char.* also sets a colour, so match that specificity to stay transparent. */
.char.game-space { display: inline-block; width: .55em; text-align: center; color: transparent; }
.char.game-space.current { color: transparent; background: var(--yellow); border-radius: 5px; }
.falling-item.pop { animation: pop 0.25s ease forwards; }
.falling-item.miss { animation: missFade 0.35s ease forwards; }
.falling-item.shake { animation: shakeItem 0.4s; }
@keyframes pop { to { transform: translateX(-50%) scale(1.6); opacity: 0; } }
@keyframes missFade { to { transform: translateX(-50%) translateY(10px); opacity: 0; } }
@keyframes shakeItem {
  10%, 90% { transform: translateX(calc(-50% - 3px)); }
  20%, 80% { transform: translateX(calc(-50% + 5px)); }
  30%, 50%, 70% { transform: translateX(calc(-50% - 8px)); }
  40%, 60% { transform: translateX(calc(-50% + 8px)); }
}

#gameCountdownOverlay {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.85);
  font-size: 90px; font-weight: bold; color: var(--pink);
  z-index: 5;
}
#gameCountdownOverlay.hidden { display: none; }

.combo-badge { color: var(--pink); }

/* ===== Mobile overrides (kept last so they beat the short-screen rules) ===== */
/* Phones and small tablets: shrink the board so a full QWERTY row still fits,
   and let the play screens scroll since a virtual keyboard eats the viewport. */
@media (max-width: 820px) {
  .key { min-width: 30px; height: 38px; font-size: 13px; border-radius: 7px; }
  .key.shiftkey { min-width: 52px; font-size: 10px; }
  .key.spacekey { min-width: 150px; }
  .key.backspacekey { min-width: 110px; font-size: 11px; }
  #keyboard { gap: 4px; } .kb-row { gap: 4px; }
  .hands-bars { width: min(320px, 86vw); }
  #fingerBanner { padding: 4px 12px; min-height: 32px; }
  #fingerLabel { font-size: 13px; } #fingerKeyChip { font-size: 13px; }
  #targetDisplay { max-width: 96vw; padding: 12px 14px; }
  :root { --flow-line: 38px; --flow-size: 1.15rem; }
  #gameField { width: 96vw; height: clamp(180px, 30vh, 260px); }
  #testWords { width: 96vw; font-size: 1.15rem; padding: 16px; }
  h1 { font-size: clamp(22px, 6vw, 32px); }
  h2 { font-size: clamp(19px, 5vw, 26px); }
  p.sub { font-size: 15px; }
  button { font-size: 16px; padding: 11px 22px; }
  .hg-key { width: 68px; min-height: 66px; }
  .hg-letter { font-size: 22px; } .hg-finger { font-size: 9px; }
  /* HUD pills wrap a lot on a narrow screen; keep them compact. */
  #hud, #gameHud, #testHud, #g2Hud { font-size: 12px; padding: 6px 8px; gap: 5px; }
  #hud .pill, #gameHud .pill, #testHud .pill, #g2Hud .pill { padding: 4px 9px; }
  #learnPlayScreen, #gamePlayScreen, #testScreen { padding-top: 96px; }
}
@media (max-width: 820px) and (max-height: 620px) {
  /* Landscape phone: the keyboard needs almost everything. */
  #targetDisplay { padding: 10px 14px; }
  :root { --flow-line: 28px; --flow-size: 1rem; }
  #fingerGuide .hands-bars { display: none; }
}

/* ===== Menu screen tidy-up ===== */
/* Was: two subtitles, a card grid, then Continue, a streak strip and a stats
   row stacked underneath. Now one subtitle, Continue as the hero action, and a
   single stats line - all above the cards. */

/* Second heatmap dimension: accurate-but-slow keys. A colour would clash with
   the accuracy scale, so slowness gets a corner dot instead. */
.heat-key.slow { position: relative; }
.heat-key.slow::after {
  content: ""; position: absolute; top: 3px; right: 3px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--purple); box-shadow: 0 0 0 1.5px var(--card);
}
.heat-legend .sw.slow-dot { background: var(--purple); border-radius: 50%; }
.weak-key.slow .weak-pct { color: var(--purple); }

/* ===== Hands resting on a keyboard (hand guide screen) =====
   Colours come from the same --f-* palette as the on-screen keyboard and the
   in-play finger guide, so a colour means the same finger everywhere. */
/* Width is capped three ways: a hard maximum, the viewport width, and the
   available *height* (34vh x the 930:450 aspect ratio). That last term is what
   keeps this screen off the scrollbar - the illustration is the tallest thing
   on it, so it has to give way first on a short laptop. */
#handsOnKb {
  width: min(980px, 96vw, calc(34vh * 2.067));
  height: auto; display: block; margin: 0 auto;
}
#handsOnKb .hk-body { fill: var(--bg2); stroke: var(--muted); stroke-width: 2; opacity: .9; }
#handsOnKb .hk-key { fill: var(--card); stroke: var(--muted); stroke-width: 1.5; }
#handsOnKb .hk-bump { fill: var(--text); opacity: .55; }

#handsOnKb .hk-key.hk-home[data-finger="lp"] { fill: var(--f-lp); }
#handsOnKb .hk-key.hk-home[data-finger="lr"] { fill: var(--f-lr); }
#handsOnKb .hk-key.hk-home[data-finger="lm"] { fill: var(--f-lm); }
#handsOnKb .hk-key.hk-home[data-finger="li"] { fill: var(--f-li); }
#handsOnKb .hk-key.hk-home[data-finger="ri"] { fill: var(--f-ri); }
#handsOnKb .hk-key.hk-home[data-finger="rm"] { fill: var(--f-rm); }
#handsOnKb .hk-key.hk-home[data-finger="rr"] { fill: var(--f-rr); }
#handsOnKb .hk-key.hk-home[data-finger="rp"] { fill: var(--f-rp); }

/* The hands sit over the board. Slight transparency keeps it readable as a
   diagram rather than hiding the keyboard underneath. */
#handsOnKb .hk-hands { opacity: .92; }
#handsOnKb .hk-palm { fill: #dbe6fa; stroke: #b9cbe8; stroke-width: 2; }
#handsOnKb .hk-finger { stroke-width: 26; stroke-linecap: round; }
#handsOnKb .hk-thumb  { stroke-width: 32; stroke-linecap: round; stroke: var(--f-thumb); }
#handsOnKb .hk-tip    { stroke: var(--card); stroke-width: 3; }

#handsOnKb .hk-finger[data-finger="lp"] { stroke: var(--f-lp); }
#handsOnKb .hk-finger[data-finger="lr"] { stroke: var(--f-lr); }
#handsOnKb .hk-finger[data-finger="lm"] { stroke: var(--f-lm); }
#handsOnKb .hk-finger[data-finger="li"] { stroke: var(--f-li); }
#handsOnKb .hk-finger[data-finger="ri"] { stroke: var(--f-ri); }
#handsOnKb .hk-finger[data-finger="rm"] { stroke: var(--f-rm); }
#handsOnKb .hk-finger[data-finger="rr"] { stroke: var(--f-rr); }
#handsOnKb .hk-finger[data-finger="rp"] { stroke: var(--f-rp); }
#handsOnKb .hk-tip[data-finger="lp"] { fill: var(--f-lp); }
#handsOnKb .hk-tip[data-finger="lr"] { fill: var(--f-lr); }
#handsOnKb .hk-tip[data-finger="lm"] { fill: var(--f-lm); }
#handsOnKb .hk-tip[data-finger="li"] { fill: var(--f-li); }
#handsOnKb .hk-tip[data-finger="ri"] { fill: var(--f-ri); }
#handsOnKb .hk-tip[data-finger="rm"] { fill: var(--f-rm); }
#handsOnKb .hk-tip[data-finger="rr"] { fill: var(--f-rr); }
#handsOnKb .hk-tip[data-finger="rp"] { fill: var(--f-rp); }

/* Drawn last, over the hands. The halo is what keeps a letter readable when a
   fingertip is resting on it — without it the home keys become unlabelled. */
#handsOnKb .hk-label {
  fill: var(--text); font: bold 21px system-ui, sans-serif; text-anchor: middle;
  stroke: var(--card); stroke-width: 3.5px; paint-order: stroke;
}
#handsOnKb .hk-label.hk-small { font-size: 16px; letter-spacing: 1px; }
#handsOnKb .hk-hand-label {
  fill: var(--muted); font: bold 17px system-ui, sans-serif; text-anchor: middle;
}

/* The palms are a large light shape; left as-is they glare on the dark themes. */
:root[data-theme="dark"] #handsOnKb .hk-palm { fill: #46567a; stroke: #61729b; }
:root[data-theme="highcontrast"] #handsOnKb .hk-palm { fill: #2a2a2a; stroke: #ffffff; }
:root[data-theme="dark"] #handsOnKb .hk-body,
:root[data-theme="highcontrast"] #handsOnKb .hk-body { opacity: .65; }

/* The finger-bar diagram also appears on the hand guide, under the big
   illustration: one shows where the hands sit, the other the finger shape on
   its own. Kept small so the screen still fits without scrolling. */
#handGuideBars { width: 100%; display: flex; justify-content: center; }
#handGuideBars .hands-bars { width: min(430px, 76vw); height: auto; }

/* ===== Persistent keyboard-layout indicator =====
   Which layout is active silently changes the finger guidance, the hand guide
   and now the lesson keys themselves, so it should never be a thing you have
   to open Settings to discover. Sits under the mute/speech buttons, out of the
   HUD's row, and is itself the shortcut to Settings. */






@media (max-width: 820px) {
  
}

/* ===== Persistent top navigation =====
   Replaces a lone Back button. Every browse screen offers the same
   destinations, so no screen is a dead end, and the bar fills the dead space
   that used to sit above every heading. */












/* Below the widest phones the labels go, leaving a row of icons. */
@media (max-width: 700px) {
  .nav-btn .nav-lbl { display: none; }
  
  
}

/* ===== "More practice" row on the course screen ===== */
.more-practice {
  width: min(var(--content-max), calc(100vw - 96px)); margin-top: 4px;
  display: flex; flex-direction: column; gap: 8px; align-items: center;
}
.mp-title {
  font-size: 13px; font-weight: bold; color: var(--muted);
  text-transform: uppercase; letter-spacing: 1px;
}
.mp-row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; width: 100%; }
@media (max-width: 900px) { .mp-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 470px) { .mp-row { grid-template-columns: 1fr; } }
.mp-card {
  display: flex; align-items: center; gap: 10px; text-align: left;
  padding: 12px 16px; border-radius: var(--r);
  background: var(--card); color: var(--text);
  box-shadow: 0 1px 2px var(--shadow);
  border: 1px solid var(--border);
}
.mp-card:hover { border-color: var(--blue); transform: translateY(-2px); }
.mp-card:active { transform: translateY(1px); }
.mp-card.locked { opacity: .5; }
.mp-emoji { font-size: 22px; line-height: 1; }
.mp-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.mp-text strong { font-size: 14px; color: var(--blue); }
.mp-text small { font-size: 11.5px; color: var(--text); opacity: .7;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ===== Screen-reader-only text =====
   Visible to assistive tech, invisible on screen. Used for input labels and
   the live region that announces screen changes. */
.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;
}
/* The skip link is the one sr-only element that must appear when focused. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--blue); color: #fff; font-weight: bold;
  padding: 10px 18px; border-radius: 0 0 12px 0; text-decoration: none;
}
.skip-link:focus { left: 0; }

/* Cards are real <button>s now, so reset the button chrome they inherit. */
.welcome-choice, .tier-card, .stage-card, .mp-card {
  font-family: inherit; border: 2px solid transparent; text-align: left;
}
.tier-card, .stage-card { text-align: center; }

/* Games screen: the learner's own history, filling what was a large empty band. */
#gamesStats {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; justify-content: center;
  background: var(--card); border-radius: 30px; padding: 10px 22px;
  font-size: 13.5px; max-width: min(760px, 92vw);
  box-shadow: 0 4px 12px var(--shadow);
}
#gamesStats .gs-item strong { color: var(--blue); font-size: 16px; }
#gamesGrid .tier-card { min-height: 210px; }

/* ===== Accessible text colours =====
   The bright palette is right for fills, borders and chips, but --blue
   (#4f9dff) and --purple (#9b7bff) as *text* only reach 2.8:1 and 3.2:1 on
   white — both below the WCAG AA 4.5:1 needed for body text. These "ink"
   variants keep the same hue at a readable weight, so the playful look is
   unchanged while the text actually passes.

   On the dark themes the bright colours sit on a dark card and already pass,
   so ink resolves back to them there. */
:root { --blue-ink: #1f6fd0; --purple-ink: #6a41c8; --nav-current-bg: #1f6fd0; }
/* --purple on the dark card lands at 4.499:1 — a hair under AA, so the dark
   theme gets a slightly lighter purple for text only. */
:root[data-theme="dark"] { --blue-ink: var(--blue); --purple-ink: #b3a0ff; --nav-current-bg: #1f6fd0; }
:root[data-theme="highcontrast"] { --blue-ink: var(--blue); --purple-ink: var(--purple); --nav-current-bg: #0d5bb0; }

h1 { color: var(--blue-ink); }
#instructions,
.chart-title,
.heat-title,
.settings-label,
.weak-title,
.result-rating,
.tier-blurb,
.weak-key.slow .weak-pct { color: var(--purple-ink); }

.tier-name,
.wc-text strong,
.mp-text strong,
.home-stat strong,
.result-wpm,
#gamesStats .gs-item strong,
#copyrightFooter a,
button.opt-btn.selected { color: var(--blue-ink); }

/* The nav's own colours moved to the .app-header block when the floating pill
   nav was replaced; they are no longer part of this ink group. */

/* --- Contrast fixes found in QA --- */

/* Dark theme: --muted is the colour of all secondary metadata ("7 levels ·
   119 lessons", progress labels). At #6b7899 on the dark card that is 3.2:1,
   under the 4.5:1 AA floor for small text. */
:root[data-theme="dark"] { --muted: #97a4c2; }

/* High-contrast theme: the base button is white text on a bright fill, which
   is 1.8:1 here — the worst contrast in the app, in the theme that exists
   specifically for contrast. Bright fills need dark ink instead.
   The nav chip is excluded: it already uses a dark fill with white text. */
:root[data-theme="highcontrast"] button:not(.nav-btn):not(.opt-btn):not(.pill-btn):not(.tier-card):not(.stage-card):not(.welcome-choice):not(.mp-card):not(.level-card):not(.hdr-brand):not(.icon-btn):not(.layout-chip):not(.sl-change) {
  /* Browse cards are <button>s with their own black background, so the dark
     ink meant for bright fills would make them black-on-black. */
  color: #000;
}
:root[data-theme="highcontrast"] button:not(.nav-btn):not(.opt-btn):not(.pill-btn):disabled {
  color: #333;
}

/* Light theme: --muted (#b7c2d6) on white is 1.8:1 — it carries the tier
   metadata ("7 levels · 117 lessons") and every progress label, so that text
   was effectively unreadable. Darkened to clear AA. Every other use of the
   token is a fill, border or axis where a darker value is also an improvement.
   (--f-thumb keeps the original pale tone: it is the thumb on the hand
   diagram, a decorative shape with no text on it.) */
:root { --muted: #6f7a94; }

/* --- Second pass of QA contrast fixes --- */

/* --muted at #6f7a94 reached 4.30:1 — still short of 4.5 for 11-12px text. */
:root { --muted: #626d87; }

/* The primary button is white on --blue (#4f9dff) = 2.76:1, which fails even
   the 3:1 large-text floor — and .small variants are 14px, needing 4.5:1.
   The ink blue keeps the same hue at 4.95:1. Same story for .secondary. */
/* (The button background is set on the base rule instead - see below. Putting
   it in an appended override outranked .tier-card and painted the cards blue,
   because browse cards are <button> elements now.) */

/* .streak-num is --pink (#ff6b9d) on the card: 2.7:1 at 19-24px bold. */
.streak-num { color: var(--pink-ink, #c62b63); }
:root[data-theme="dark"], :root[data-theme="highcontrast"] { --pink-ink: var(--pink); }

/* --- QA pass 3: fallout from cards becoming <button>s --- */

/* A <button> inherits `color: white` from the base rule. Cards set their own
   background but not their colour, so any card text without an explicit colour
   (.stage-name, .stars-mini) rendered white-on-white — invisible. */
.welcome-choice, .tier-card, .stage-card, .mp-card, .level-card { color: var(--text); }

/* Button fills need a token of their own. --blue-ink resolves to the *bright*
   colour on the dark themes (correct for text sitting on a dark card, wrong as
   a fill behind white text), which pushed the buttons back to 2.8:1 there.
   The fill is fixed across light/dark because its contrast is against its own
   label, not the page. High contrast keeps bright fills with black ink. */
:root, :root[data-theme="dark"] { --btn-bg: #1f6fd0; --btn-bg-2: #6a41c8; }
:root[data-theme="highcontrast"] { --btn-bg: var(--blue); --btn-bg-2: var(--purple); }
button { background: var(--btn-bg); }
button.secondary { background: var(--btn-bg-2); }

/* --- QA pass 4 --- */

/* The high-contrast "dark ink on bright fill" rule also matched the browse
   cards, which are <button>s with their own black background — black on black.
   Cards restate their colour at matching specificity. */
:root[data-theme="highcontrast"] .welcome-choice,
:root[data-theme="highcontrast"] .tier-card,
:root[data-theme="highcontrast"] .stage-card,
:root[data-theme="highcontrast"] .mp-card,
:root[data-theme="highcontrast"] .level-card { color: var(--text); }

/* Star ratings were --yellow on white: 1.4:1. Filled and empty stars were
   nearly indistinguishable. A deeper amber keeps the "gold star" read while
   clearing AA. The dark themes already have enough contrast. */
:root { --star-ink: #8a6100; }
:root[data-theme="dark"], :root[data-theme="highcontrast"] { --star-ink: var(--yellow); }
.stars-mini, .stars { color: var(--star-ink); }

/* Destructive action: visually distinct from the neutral option buttons. */
button.opt-btn.danger { border-color: var(--red); color: var(--red-ink); }
button.opt-btn.danger:hover { background: var(--red); color: #fff; }

/* ===========================================================================
   FULL-WIDTH APPLICATION HEADER
   ---------------------------------------------------------------------------
   Replaces the old floating pill nav plus three separately-positioned corner
   buttons. One bar, three zones, spanning the viewport.

   Hidden entirely on play screens: those fill the window by design, must never
   scroll, and carry their own Back inside the HUD. `body.play-mode` also drops
   the content offset so nothing shifts.
   =========================================================================== */
.app-header {
  position: fixed; inset: 0 0 auto 0; z-index: 60;
  height: 56px;
  background: var(--card);
  border-bottom: 1px solid var(--border);
  box-shadow: 0 1px 2px var(--shadow);
}
.app-header.hidden { display: none; }

.hdr-inner {
  height: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 20px;
  display: flex; align-items: center; gap: 16px;
}

.hdr-left  { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.hdr-nav   { display: flex; align-items: center; gap: 2px; margin: 0 auto; }
.hdr-right { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

/* Brand */
.hdr-brand {
  display: flex; align-items: center; gap: 8px;
  background: none; border: none; box-shadow: none;
  padding: 6px 8px; border-radius: var(--r);
  color: var(--text); font-size: 15px; font-weight: 700;
  letter-spacing: -0.01em; white-space: nowrap;
}
.hdr-brand:hover { background: var(--bg2); }
.hdr-logo { font-size: 20px; line-height: 1; }

/* Navigation buttons */
.nav-btn {
  display: flex; align-items: center; gap: 7px;
  padding: 8px 12px; border-radius: var(--r);
  background: transparent; color: var(--muted);
  font-size: 14px; font-weight: 600; line-height: 1;
  box-shadow: none; border: 1px solid transparent;
  white-space: nowrap;
  transition: background .12s, color .12s;
}
.nav-btn:hover { background: var(--bg2); color: var(--text); }
.nav-btn:active { transform: none; }
.nav-btn.current { background: var(--blue-soft); color: var(--nav-cur-ink); }
.nav-btn.current:hover { background: var(--blue-soft); color: var(--nav-cur-ink); }
.nav-btn.hidden { display: none; }

.nav-btn.nav-back {
  background: transparent; color: var(--muted);
  border: 1px solid var(--border);
}
.nav-btn.nav-back:hover { background: var(--bg2); color: var(--text); filter: none; }

/* Utility controls: square icon buttons, not floating circles. */
.icon-btn {
  width: 34px; height: 34px; padding: 0;
  display: grid; place-items: center;
  font-size: 16px; line-height: 1;
  background: transparent; color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  box-shadow: none;
}
.icon-btn:hover { background: var(--bg2); }
.icon-btn:active { transform: none; }
.speech-btn { opacity: .8; }
.speech-btn.on { opacity: 1; background: var(--blue-soft); border-color: var(--blue); }

.layout-chip {
  display: flex; align-items: center; gap: 7px;
  padding: 7px 11px; border-radius: var(--r-sm);
  background: transparent; color: var(--muted);
  font-size: 13px; font-weight: 600;
  border: 1px solid var(--border); box-shadow: none;
  max-width: 190px;
}
.layout-chip:hover { background: var(--bg2); color: var(--text); }
.layout-chip:active { transform: none; }
.layout-chip .lc-icon { font-size: 14px; line-height: 1; }
.layout-chip .lc-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.layout-chip.hidden { display: none; }

/* Content sits below the fixed bar. Play screens remove the offset. */
#wrap { padding-top: 56px; box-sizing: border-box; }
body.play-mode #wrap { padding-top: 0; }

/* Narrow screens: drop the nav labels, then the brand name, then the chip.
   The controls stay reachable at every width rather than wrapping the bar. */
@media (max-width: 1100px) {
  .layout-chip { max-width: 130px; }
}
@media (max-width: 900px) {
  .nav-btn .nav-lbl { display: none; }
  .nav-btn { padding: 8px 10px; font-size: 16px; }
  .hdr-inner { gap: 10px; padding: 0 14px; }
}
@media (max-width: 720px) {
  .hdr-name { display: none; }
  .layout-chip .lc-name { display: none; }
  .layout-chip { max-width: none; padding: 7px 9px; }
}
@media (max-width: 520px) {
  .nav-btn.nav-back .nav-lbl { display: none; }
  .hdr-inner { gap: 6px; padding: 0 10px; }
  .hdr-right { gap: 5px; }
}


/* ---------------------------------------------------------------------------
   Header contrast — three fixes found by the post-restyle audit.

   The header controls are <button>s, so the high-contrast rule that puts BLACK
   ink on bright fills matched them too. Their fills are transparent over
   --card, which is black in that theme, so the entire header rendered
   black-on-black — brand, layout chip, mute and speech all invisible. They join
   the exclusion list and restate the readable ink, exactly as the browse cards
   already do a few rules above.
   --------------------------------------------------------------------------- */
:root[data-theme="highcontrast"] .hdr-brand,
:root[data-theme="highcontrast"] .icon-btn,
:root[data-theme="highcontrast"] .layout-chip { color: var(--text); background: transparent; }

/* The current-page pill. --blue-ink is tuned against a white card; --blue-soft
   is lighter still, which cost it the 4.5:1 floor at 14px. */
:root { --nav-cur-ink: #14509f; }                                    /* 6.4:1 on #dbeafe */
:root[data-theme="dark"] { --nav-cur-ink: var(--blue-ink); }         /* tint is navy here */
:root[data-theme="highcontrast"] { --nav-cur-ink: var(--blue-ink); }

/* Destructive ink. Dark on a light card, light on a dark one — the same colour
   cannot clear 4.5:1 against both. */
:root { --red-ink: #c81e1e; }                                        /* 5.7:1 on white */
:root[data-theme="dark"] { --red-ink: #f87171; }                     /* 5.3:1 on #1e293b */
:root[data-theme="highcontrast"] { --red-ink: #ff8a8a; }


/* ===========================================================================
   START SCREEN — DASHBOARD LAYOUT
   ---------------------------------------------------------------------------
   The start screen used to be a 520px column centred in the viewport, which
   left roughly 70% of a desktop window empty. It is now a hero banner over a
   two-zone body: actions on the left, an at-a-glance panel on the right.

   #welcomeScreen keeps .screen's centring, so a short dashboard still sits in
   the middle of the window rather than clinging to the top.
   =========================================================================== */
.dash {
  width: min(var(--content-max), calc(100vw - 96px));
  display: flex; flex-direction: column; gap: 18px;
  text-align: left;
}

.hero-card {
  display: flex; align-items: center; gap: 24px;
  padding: 22px 30px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: 0 1px 2px var(--shadow), 0 4px 12px var(--shadow);
}
.hero-copy { min-width: 0; }
.hero-card h2 { margin: 0 0 4px; text-align: left; }
.hero-card .sub { margin: 0; text-align: left; max-width: var(--prose-max); }

.dash-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 18px; align-items: start;
}
.dash-side { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

/* Panels in the side column */
.side-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 16px 18px;
  box-shadow: 0 1px 2px var(--shadow), 0 4px 12px var(--shadow);
}
.side-title {
  font-size: 11.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
  margin-bottom: 12px;
}
.stat-row {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; padding: 7px 0;
  border-bottom: 1px solid var(--border);
}
.stat-row:last-child { border-bottom: none; padding-bottom: 0; }
.stat-label { font-size: 13px; color: var(--muted); }
.stat-value { font-size: 19px; font-weight: 700; color: var(--text); }
.stat-value .stat-unit { font-size: 11.5px; font-weight: 600; color: var(--muted); margin-left: 3px; }

.side-hint { font-size: 12.5px; line-height: 1.55; color: var(--muted); margin: 0; }
.side-hint + .side-hint { margin-top: 9px; }

.side-layout {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.side-layout .sl-name { font-size: 14px; font-weight: 700; color: var(--text); }
.side-layout .sl-sub { font-size: 11.5px; color: var(--muted); }
button.sl-change {
  padding: 7px 12px; font-size: 12.5px; font-weight: 600;
  background: transparent; color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  box-shadow: none; flex-shrink: 0;
}
button.sl-change:hover { background: var(--bg2); filter: none; }
button.sl-change:active { transform: none; }

/* The streak strip reads as a row of chips inside the panel, not a centred
   banner under the title. */
#welcomeStats { justify-content: flex-start; }

/* Below 1000px the side panel drops under the actions; the action grid keeps
   its own auto-fit behaviour and reaches one column at ~700px. */
@media (max-width: 1000px) {
  .dash-body { grid-template-columns: minmax(0, 1fr); }
  .dash { width: min(var(--content-max), calc(100vw - 40px)); }
}
@media (max-width: 768px) {
  .hero-card { flex-direction: column; text-align: center; gap: 12px; padding: 18px 20px; }
  .hero-card h2, .hero-card .sub { text-align: center; }
  .dash { gap: 14px; width: calc(100vw - 28px); }
}


/* The current-page tint is light in the default theme and DARK in the other
   two, so a single ink cannot serve all three — the first attempt used the
   same blue everywhere and dropped to 2.0:1 on the navy tint. */
:root[data-theme="dark"] { --nav-cur-ink: #c7dcff; }                 /* 7.5:1 on #1e3a8a */
:root[data-theme="highcontrast"] { --nav-cur-ink: #cfe9ff; }         /* 10.7:1 on #003347 */

/* Resume title. --green on the card's own green tint is 2.35:1. */
:root { --green-ink: #097a58; }                                      /* 5.0:1 on the tint */
:root[data-theme="dark"], :root[data-theme="highcontrast"] { --green-ink: #34d399; }

/* Header controls in high contrast: transparent fills over a black bar, so they
   need the theme's own foreground, not the dark ink meant for bright fills. */
:root[data-theme="highcontrast"] .hdr-brand,
:root[data-theme="highcontrast"] .icon-btn,
:root[data-theme="highcontrast"] .sl-change,
:root[data-theme="highcontrast"] .layout-chip { color: var(--text); background: transparent; }


/* ---------------------------------------------------------------------------
   opacity and contrast

   Six text nodes failed AA purely because of an opacity that the earlier audit
   never measured — it read the computed `color` and stopped there, while the
   pixels the eye actually sees are that colour blended toward the background.
   Anything carrying text now states its colour instead of fading it.

   The exception is a genuinely disabled control (.mp-card.locked): WCAG 1.4.3
   exempts inactive controls, and fading it is how "locked" is communicated.
   The speech toggle is NOT one of those — it is live, and clicking it is how
   speech gets turned on — so its "off" state fades only as far as AA allows.
   --------------------------------------------------------------------------- */
/* .heat-val deliberately has NO colour of its own: it inherits #12203a from
   .heat-key, which clears 7:1 on all four accuracy fills. Setting --muted here
   overrode that and dropped it to 1.4:1 on the dark themes. */

/* ===========================================================================
   ARTICLE CONTENT ON AN APP PAGE
   ---------------------------------------------------------------------------
   typing-test.html keeps the ~380 words it already ranks on, on the same URL
   as the tool. The copy lives in a browse screen (which may scroll); the test
   itself is a play screen (which may not).

   landing.css is deliberately NOT loaded here — it styles bare `body`, `h1`,
   `h2`, `ul` and `table`, which would fight the app's own rules. The handful
   of classes the article needs are restated below instead.
   =========================================================================== */
.article {
  width: min(var(--prose-max), calc(100vw - 48px));
  margin: 0 auto;
  text-align: left;
}
.article-title { text-align: center; margin-bottom: 6px; }
.article .sub { text-align: center; }
.article-cta { justify-content: center; margin: 22px 0 30px; }

.article h2 {
  font-size: 20px; margin: 30px 0 8px;
  color: var(--text); text-align: left;
}
.article h3 { font-size: 16px; margin: 18px 0 4px; color: var(--blue-ink); }
.article p { font-size: 15px; line-height: 1.65; color: var(--text); margin: 0 0 10px; }
.article ul, .article ol { padding-left: 22px; margin: 0 0 12px; }
.article li { margin: 5px 0; font-size: 15px; line-height: 1.6; color: var(--text); }

.article .grid2 {
  display: grid; gap: 14px; margin: 14px 0;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}
.article .card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 16px 18px;
  box-shadow: 0 1px 2px var(--shadow);
}
.article .card h3 { margin-top: 0; }
.article .card p:last-child { margin-bottom: 0; }

.article table { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: 14.5px; }
.article th, .article td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--border); }
.article th { color: var(--text); font-weight: 700; }

/* The intro is a browse screen: it scrolls from the top rather than centring,
   because an article is taller than the viewport by design. */
#testIntroScreen { justify-content: flex-start; padding-top: 24px; }

/* ---------------------------------------------------------------------------
   The header nav is <a href> now, not <button>.

   Real links are what a multi-page app should use: crawlable, middle-clickable,
   and they show their target in the status bar. But an anchor inherits none of
   the button reset, so the shared look has to be restated here — and the base
   `a { color: … ; text-decoration: underline }` rule would otherwise show
   through as underlined blue nav items.
   --------------------------------------------------------------------------- */
a.nav-btn, a.hdr-brand, a.layout-chip {
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
a.nav-btn:hover, a.hdr-brand:hover, a.layout-chip:hover { text-decoration: none; }
/* Anchors are inline by default; the zones rely on these being flex boxes. */
a.nav-btn { display: flex; }
a.hdr-brand { display: flex; }
a.layout-chip { display: flex; }

/* ===========================================================================
   LEARN PLAY SCREEN — three-column practice layout
   ---------------------------------------------------------------------------
   The old layout was one centred column with `justify-content: space-between`,
   which pinned the text box to the top and the keyboard to the bottom. On a
   1920x1080 display that left a 326px void between the thing you read and the
   thing you look down at — the worst possible place for dead space on a screen
   whose entire job is the glance between the two.

   Now: one vertically-centred block, with the horizontal room on a wide
   monitor spent on what you are practising (left) and how you are doing
   (right) instead of on nothing.

   Play screens must NEVER scroll — see CLAUDE.md section 5. Every size below
   is chosen against that, and the short-screen block further down shrinks the
   tokens rather than letting anything overflow.
   =========================================================================== */
:root {
  /* Typing box: the clipper height is line x count, so it is always an exact
     number of lines and can never show half of one. */
  --flow-line: 56px;
  --flow-lines: 3;
  --flow-size: 2rem;
  /* Keyboard: one place to scale the whole board. */
  --key-size: 46px;
  --key-gap: 8px;
  --key-font: 17px;
}

.play-grid {
  display: grid;
  grid-template-columns: 250px minmax(0, 1fr) 250px;
  grid-template-areas: "l m r";
  gap: 20px;
  align-items: center;
  justify-items: center;
  width: min(1680px, calc(100vw - 36px));
}
.play-left  { grid-area: l; }
.play-main  { grid-area: m; }
.play-right { grid-area: r; }

.play-main {
  display: flex; flex-direction: column; align-items: center;
  gap: 10px; min-width: 0; width: 100%;
}
.play-side {
  display: flex; flex-direction: column; gap: 10px;
  width: 100%; align-self: center;
}

.ps-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 12px 14px;
  box-shadow: 0 1px 2px var(--shadow);
  text-align: left;
  min-width: 0;
}
.ps-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
}
.ps-stage { font-size: 15px; font-weight: 700; color: var(--text); margin-top: 3px; line-height: 1.25; }
.ps-sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.ps-stat { text-align: center; }
.ps-stat .ps-label { margin-top: 2px; }
.ps-big { font-size: 30px; font-weight: 800; color: var(--text); line-height: 1.1; }
.ps-big.ps-err { color: var(--red-ink); }
.ps-big.stars-mini { font-size: 22px; color: var(--star-ink); }

/* The keys this lesson uses, as small chips. */
.ps-keys { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.ps-keys span {
  min-width: 24px; padding: 3px 6px;
  background: var(--bg2); border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: 13px; font-weight: 700; text-align: center; color: var(--text);
}
.ps-home span { background: var(--blue-soft); border-color: var(--blue); }

/* The progress bar sits in the right panel now, so it fills its card. */
#progressDots { display: block; max-width: none; margin-top: 6px; }

/* ---------------------------------------------------------------------------
   Breakpoints, in cascade order. All of them drive the same tokens, so the
   typing box is always a whole number of lines and the keyboard always has a
   vertical budget that fits — play screens must never scroll (CLAUDE.md §5).

   Order matters: height rules first, width rules last, so a tall narrow phone
   gets phone sizing rather than the short-laptop sizing.
   --------------------------------------------------------------------------- */

/* Roomy desktop: spend the space on legibility. */
@media (min-width: 1500px) and (min-height: 860px) {
  :root { --key-size: 58px; --key-gap: 9px; --key-font: 20px; --flow-size: 2.15rem; }
}

/* Laptop heights (1366x768, 1280x720). The vertical budget at 720px is the
   binding constraint: 5 keyboard rows at 38px + 4 gaps at 6px = 214px, which
   is exactly what is left after the panels, text box and hand guide. Larger
   keys here push the SPACE row off the bottom edge. */
@media (max-height: 820px) {
  :root {
    --flow-line: 42px; --flow-lines: 3; --flow-size: 1.55rem;
    --key-size: 38px; --key-gap: 6px; --key-font: 15px;
  }
  #learnPlayScreen { padding: 46px 16px 10px; }
  .play-main { gap: 7px; }
  #targetDisplay { padding: 12px 20px; }
}

/* Netbooks and small laptop windows: two lines rather than a clipped third. */
@media (max-height: 680px) {
  :root { --flow-lines: 2; --key-size: 34px; --key-gap: 5px; --key-font: 13px; }
}

/* ---------------------------------------------------------------------------
   Below 1500px there is no room for side columns, so both panels become
   compact chip rows above the typing area — which is what the old top HUD
   looked like. Nothing is hidden; it only gets denser.
   --------------------------------------------------------------------------- */
@media (max-width: 1499px) {
  .play-grid {
    grid-template-columns: 1fr 1fr;
    grid-template-areas: "l r" "m m";
    gap: 8px 12px;
    width: min(1200px, calc(100vw - 28px));
  }
  .play-side { flex-direction: row; flex-wrap: wrap; gap: 8px; justify-content: center; }
  .play-left { justify-content: flex-start; }
  .play-right { justify-content: flex-end; }
  .ps-card {
    display: flex; align-items: baseline; gap: 7px;
    padding: 5px 10px; box-shadow: none;
  }
  .ps-label { font-size: 10px; }
  .ps-stage { font-size: 13px; margin-top: 0; }
  .ps-sub { margin-top: 0; }
  .ps-big { font-size: 16px; }
  .ps-big.stars-mini { font-size: 15px; }
  .ps-keys { margin-top: 0; }
  .ps-keys span { font-size: 11.5px; padding: 1px 5px; min-width: 20px; }
  .ps-stat { text-align: left; }
  #progressDots { margin-top: 0; min-width: 150px; }
}

@media (max-width: 1100px) {
  .play-grid { grid-template-columns: 1fr; grid-template-areas: "l" "r" "m"; }
  /* The home-row card is the least useful when space is tight: the on-screen
     keyboard already shows the bumps. */
  .play-left .ps-card:nth-child(3) { display: none; }
}

/* Phones. Last, so width wins over the height rules above.
   640, not 768: an iPad in portrait is exactly 768 wide and has the room for
   full-size keys — capturing it here shrank the board for no reason. */
@media (max-width: 640px) {
  :root {
    --flow-line: 34px; --flow-lines: 3; --flow-size: 1.2rem;
    --key-size: 30px; --key-gap: 4px; --key-font: 12px;
  }
  .play-grid { width: calc(100vw - 14px); gap: 5px; }
  .ps-card { padding: 4px 8px; }
  .ps-big { font-size: 14px; }
  .ps-label { font-size: 9px; }
  #targetDisplay { padding: 10px 12px; max-width: 100%; }
  /* Progress and stars go; WPM, accuracy and mistakes are what matter mid-drill. */
  .play-right .ps-card:nth-child(4),
  .play-right .ps-card:nth-child(5) { display: none; }
}
