/* ===========================================================================
   PacLingo — "Lexicon Aurora"

   The visual language of the game, and deliberately not a maze-game pastiche.
   Three commitments run through every rule below:

   1. ORIGINALITY. The ground is deep indigo, the hero reads jade, the collectibles
      are amber crystals and violet runes. Nothing here is black-and-yellow, and no
      shape quotes another product. The mark is a speech bubble, because that is what
      the game is actually about.

   2. LEGIBLE AT ANY AGE. Nothing interactive is smaller than 44px, body copy starts at
      1rem and scales to 1.25rem on request, and the dim text tone is held above a 4.5:1
      ratio on its own surface in both themes — not the 3:1 that dark interfaces usually
      drift to.

   3. NEVER COLOUR ALONE. Right and wrong, weak and strong, safe and dangerous each carry
      a shape or a word as well as a hue. The .flag-* and .bar-* families below exist
      precisely so no state is distinguishable by colour only.

   The renderer reads its palette from the --c-* tokens with getComputedStyle, so the
   canvas and the DOM cannot drift apart, and a theme switch repaints the game.
   =========================================================================== */
:root {
  --radius: 20px;
  --radius-sm: 14px;
  --radius-xs: 10px;
  --font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Outfit", var(--font);
  --tap: 44px;                 /* the floor for anything you can press */

  /* One rhythm for the whole play screen. Every vertical gap between the top bar, the
     objective, the HUD, the strip and the board is one of these — which is the
     difference between spacing that looks decided and spacing that looks left over. */
  --space-1: .35rem;
  --space-2: .6rem;
  --space-3: .9rem;
  --space-4: 1.3rem;
  --gutter: .9rem;
  --ui-scale: 1;
  --pad-size: 186px;
}

/* --------------------------------------------------------------- Dark ---- */
[data-theme="dark"] {
  --bg: #080D1A;
  --bg-2: #0E1526;
  --surface: #141C3399;
  --surface-solid: #141C33;
  --surface-2: #1E2743;
  --surface-3: #27325A;
  --text: #EEF2FF;
  --text-dim: #A9B6D6;
  --border: #2C3860;
  --border-soft: #ffffff1a;
  --primary: #7C6CFF;
  --primary-2: #A996FF;
  --secondary: #2BD9BC;
  --highlight: #FFB454;
  --danger: #FF7A90;
  --ok: #2BD9BC;
  --shadow-1: 0 1px 2px rgba(0,0,0,.5), 0 6px 18px rgba(0,0,0,.38);
  --shadow-2: 0 2px 10px rgba(0,0,0,.55), 0 22px 48px rgba(0,0,0,.5);
  --glow: 0 0 0 1px #ffffff12, 0 0 24px -6px var(--primary);

  /* ---- canvas ---- */
  --c-bg: #080D1A;
  --c-lane: #17203A;
  --c-lane-edge: #27345C;
  --c-wall: #0C1224;
  --c-wall-ink: #1A2440;
  --c-mote: #7E8DBE;
  --c-hero: #2BD9BC;
  --c-hero-2: #B6FFF1;
  --c-hero-ink: #06222B;
  --c-crystal: #FFB454;
  --c-crystal-2: #FFE3B0;
  --c-rune: #A996FF;
  --c-foe-1: #FF7A90;
  --c-foe-2: #56C2FF;
  --c-foe-3: #7C6CFF;
  --c-foe-4: #FFB454;
  --c-foe-calm: #48568C;
  --c-foe-core: #EEF2FF;
  --c-pill: #F2F5FF;
  --c-pill-ink: #101830;
  --c-pill-ink-dim: #5A688F;
  --c-pill-edge: #ffffff2e;
  --c-text: #EEF2FF;
  --c-accent: #2BD9BC;

  /* Per-world tints. The renderer looks for --c-<token>-<world> before the default. */
  --c-lane-library: #221F33;      --c-lane-edge-library: #3B3357;   --c-mote-library: #C7A473;   --c-wall-library: #14101F;  --c-wall-ink-library: #2A2340;
  --c-lane-city: #10233A;         --c-lane-edge-city: #1E4670;      --c-mote-city: #6FB6E0;      --c-wall-city: #0A1626;     --c-wall-ink-city: #17304F;
  --c-lane-data: #0F2029;         --c-lane-edge-data: #1B4A45;      --c-mote-data: #56D3AE;      --c-wall-data: #08161C;     --c-wall-ink-data: #143A36;
}

/* --------------------------------------------------------------- Light --- */
[data-theme="light"] {
  --bg: #EEF0F9;
  --bg-2: #F8F9FE;
  --surface: #ffffffd9;
  --surface-solid: #ffffff;
  --surface-2: #EFF1FA;
  --surface-3: #E1E5F5;
  --text: #10162B;
  --text-dim: #56618A;
  --border: #D8DDEF;
  --border-soft: #0b132a17;
  --primary: #5A46E0;
  --primary-2: #7C6CFF;
  --secondary: #0E9E86;
  --highlight: #B36B00;
  --danger: #D3284A;
  --ok: #0E9E86;
  --shadow-1: 0 1px 2px rgba(20,30,70,.10), 0 6px 16px rgba(20,30,70,.10);
  --shadow-2: 0 2px 10px rgba(20,30,70,.13), 0 22px 46px rgba(20,30,70,.18);
  --glow: 0 0 0 1px #0b132a12, 0 0 24px -8px var(--primary);

  --c-bg: #E4E8F7;
  --c-lane: #FFFFFF;
  --c-lane-edge: #CBD4EC;
  --c-wall: #D3DAF0;
  --c-wall-ink: #BAC4E2;
  --c-mote: #8794BC;
  --c-hero: #0E9E86;
  --c-hero-2: #55E8CC;
  --c-hero-ink: #FFFFFF;
  --c-crystal: #C2760A;
  --c-crystal-2: #FFCF87;
  --c-rune: #5A46E0;
  --c-foe-1: #D3284A;
  --c-foe-2: #0B76C4;
  --c-foe-3: #5A46E0;
  --c-foe-4: #A85E00;
  --c-foe-calm: #9AA5C8;
  --c-foe-core: #10162B;
  --c-pill: #1A2340;
  --c-pill-ink: #F5F7FF;
  --c-pill-ink-dim: #A6B2D6;
  --c-pill-edge: #00000026;
  --c-text: #10162B;
  --c-accent: #0E9E86;

  /* Light-theme worlds, re-measured. Corridor against wall used to sit at 1.19–1.26,
     which is not a difference an eye resolves while something is chasing it — the maze
     read as texture rather than as walls. These land at 1.71–1.73 with the wall texture
     a further step darker again (2.4–2.6), and each world keeps its own hue: the library
     stays warm, the city stays blue, the data world stays green. */
  --c-lane-library: #FDFAF3;      --c-lane-edge-library: #C4AC81;   --c-mote-library: #7A5F2E;   --c-wall-library: #D3C09A;  --c-wall-ink-library: #B2996B;
  --c-lane-city: #F7FBFF;         --c-lane-edge-city: #9BB8D6;      --c-mote-city: #3F6B93;      --c-wall-city: #AFC3DC;     --c-wall-ink-city: #8AA6C6;
  --c-lane-data: #F5FDFA;         --c-lane-edge-data: #92C0AB;      --c-mote-data: #26775C;      --c-wall-data: #A9C9B8;     --c-wall-ink-data: #83AC97;
}

/* A separate axis from the theme: some readers need more separation in either one. */
[data-contrast="high"] {
  --border: color-mix(in srgb, var(--text) 45%, transparent);
  --border-soft: color-mix(in srgb, var(--text) 32%, transparent);
  --surface: var(--surface-solid);
  --c-pill-edge: currentColor;
}
[data-contrast="high"][data-theme="dark"] { --text-dim: #C9D4EE; --c-lane-edge: #4C5F96; --c-mote: #A8B6E0; }
[data-contrast="high"][data-theme="light"] { --text-dim: #3B456A; --c-lane-edge: #97A6C8; --c-mote: #5B688F; }
/* The two lines above cannot reach the maze on their own: the renderer prefers the
   per-world token (--c-lane-edge-library and friends), so overriding the generic one
   here changed nothing on any board. Rather than restate every world by hand — five
   tokens × three worlds × two themes, and a new world would silently miss out — the
   renderer reads this flag and widens the separation of whatever palette it loaded.
   See readPalette() in modules/render.js. */

/* ---------------------------------------------------------------------------
   Scrollbars.

   There were none — the browser's own were showing through everywhere, complete with
   the stepper arrows at each end, which is the one piece of chrome that instantly reads
   as "nobody styled this". They are drawn here instead, once, for every scrolling thing
   in the game: the page, the modals, the help centre's tab strip and any panel that
   grows past its box.

   Two engines, two vocabularies, same result:
     · Firefox takes `scrollbar-width` + `scrollbar-color` and nothing else. Thin, and a
       thumb/track pair from the theme tokens.
     · WebKit and Blink take the ::-webkit-scrollbar pseudo-elements, which is where the
       rounded thumb, the quiet track and — importantly — `::-webkit-scrollbar-button`
       set to zero come from. That last one is what removes the arrows.

   Deliberately restrained: 10px, a thumb the weight of a border, a track that is a wash
   rather than a channel. It should be findable at a glance and forgettable the rest of
   the time. The thumb lifts toward the brand colour on hover, which is the only moment
   it is meant to be noticed.

   Tokens, not literals, so light, dark and high contrast are all handled by the same
   rules — the palette underneath them already changes.
   --------------------------------------------------------------------------- */
:root {
  --sb-size: 10px;
  --sb-thumb: color-mix(in srgb, var(--text-dim) 38%, transparent);
  --sb-thumb-hover: color-mix(in srgb, var(--primary) 62%, var(--text-dim));
  --sb-track: color-mix(in srgb, var(--text) 6%, transparent);
}
/* High contrast is a legibility setting, so the scrollbar stops being subtle too. */
[data-contrast="high"] {
  --sb-thumb: color-mix(in srgb, var(--text) 55%, transparent);
  --sb-thumb-hover: var(--primary);
  --sb-track: color-mix(in srgb, var(--text) 14%, transparent);
}

/* Two engines, and they must not both be addressed at once.
   ---------------------------------------------------------------------------
   The standard properties (`scrollbar-width` / `scrollbar-color`) and the WebKit
   pseudo-elements are not additive: in Chrome 121+ a specified `scrollbar-color` makes
   the engine DISCARD the ::-webkit- rules and fall back to its own thin scrollbar —
   which on Linux still draws a stepper arrow at each end. Setting both therefore gets
   the arrows back, which is the single thing this was written to remove.

   So Blink and WebKit get the pseudo-elements alone, which is the only route that can
   turn the steppers off, and the standard properties are scoped to the engine that has
   no pseudo-elements to offer. Firefox never draws steppers, so nothing is lost there.

   (`scrollbar-color` inherits; `scrollbar-width` does not. Hence the universal selector
   rather than a rule on `html` — a modal is a separate scroller and would otherwise
   keep the system default.) */
@supports not selector(::-webkit-scrollbar) {
  * { scrollbar-width: thin; scrollbar-color: var(--sb-thumb) var(--sb-track); }
}

/* Applied to everything rather than to a list of containers, because a container that
   scrolls and was left off that list is exactly the bug this replaces. */
*::-webkit-scrollbar { width: var(--sb-size); height: var(--sb-size); }
*::-webkit-scrollbar-track { background: var(--sb-track); border-radius: 999px; }
*::-webkit-scrollbar-thumb {
  background: var(--sb-thumb);
  border-radius: 999px;
  /* A transparent border with background-clip is the trick that insets the thumb inside
     the track without needing a second element: the border shows the track through it,
     so the thumb reads as narrower than the gutter it runs in. */
  border: 2px solid transparent;
  background-clip: padding-box;
  transition: background-color .16s ease;
}
*::-webkit-scrollbar-thumb:hover { background: var(--sb-thumb-hover); background-clip: padding-box; }
*::-webkit-scrollbar-thumb:active { background: var(--primary); background-clip: padding-box; }
/* The steppers. Zeroing these is what removes the arrows at each end. */
*::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
/* The square where a vertical and a horizontal bar meet, which otherwise stays default. */
*::-webkit-scrollbar-corner { background: transparent; }

/* Touch platforms overlay their own scrollbars, which appear on scroll and fade out.
   That is the right behaviour there and it is not ours to override, so the styled
   gutter is not forced onto a device that does not reserve space for one. */
@media (pointer: coarse) {
  @supports not selector(::-webkit-scrollbar) {
    * { scrollbar-width: auto; scrollbar-color: auto; }
  }
  *::-webkit-scrollbar { width: 0; height: 0; }
}

@media (prefers-reduced-motion: reduce) { *::-webkit-scrollbar-thumb { transition: none; } }
[data-motion="reduce"] *::-webkit-scrollbar-thumb { transition: none; }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
html { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
body {
  font-family: var(--font);
  font-size: calc(1rem * var(--ui-scale));
  line-height: 1.45;
  background:
    radial-gradient(1200px 760px at 88% -16%, color-mix(in srgb, var(--primary) 26%, transparent), transparent 62%),
    radial-gradient(900px 660px at -10% 110%, color-mix(in srgb, var(--secondary) 15%, transparent), transparent 60%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  min-height: 100dvh;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior: none;
}
.hidden { display: none !important; }
button { font-family: inherit; cursor: pointer; }
h1, h2, h3, h4 { margin: 0; font-family: var(--font-display); letter-spacing: -.015em; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
table { border-collapse: collapse; width: 100%; }
svg { display: block; }

.skip-link { position: absolute; left: -999px; top: 0; background: var(--primary); color: #fff; padding: .6rem 1.1rem; z-index: 100; border-radius: 0 0 10px 0; font-weight: 650; }
.skip-link:focus { left: 0; }
/* A thick, offset ring: keyboard focus has to survive being over a busy canvas. */
:focus-visible { outline: 3px solid var(--primary-2); outline-offset: 3px; border-radius: 8px; }

/* -------------------------------- Buttons ------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: .55rem;
  min-height: var(--tap);
  background: var(--surface-2); color: var(--text);
  border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  padding: .7rem 1.15rem; font-size: .98rem; font-weight: 650;
  transition: transform .1s ease, background .16s ease, border-color .16s ease;
}
.btn svg { width: 19px; height: 19px; flex: none; }
.btn:hover { background: var(--surface-3); border-color: color-mix(in srgb, var(--primary) 50%, var(--border)); }
.btn:active { transform: translateY(1px) scale(.985); }
.btn-primary {
  background: linear-gradient(135deg, var(--primary), var(--primary-2));
  border-color: transparent; color: #fff; box-shadow: var(--shadow-1);
}
.btn-primary:hover { filter: brightness(1.08); }
.btn-lg { width: 100%; min-height: 56px; padding: 1rem 1.25rem; font-size: 1.08rem; }
.btn-danger { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, var(--border)); }

.icon-btn {
  display: inline-grid; place-items: center;
  width: var(--tap); height: var(--tap); min-width: var(--tap); min-height: var(--tap); flex: none;
  background: var(--surface); color: var(--text);
  border: 1.5px solid var(--border-soft); border-radius: var(--radius-xs);
  transition: background .16s ease, transform .1s ease, border-color .16s ease;
}
.icon-btn svg { width: 21px; height: 21px; }
.icon-btn:hover { background: var(--surface-2); border-color: var(--border); }
.icon-btn:active { transform: scale(.94); }

/* -------------------------------- Layout -------------------------------- */
.screen { min-height: 100dvh; }
.shell {
  width: min(1000px, 100%);
  margin: 0 auto;
  padding: max(1rem, env(safe-area-inset-top)) 1rem calc(1.6rem + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: 1.25rem;
}
.screen-head { display: flex; align-items: center; gap: .8rem; }
/* Pushed to the far right of the header row, which puts it above the "I want to learn"
   column without adding a row or crowding the title. */
.screen-head .head-action { margin-left: auto; }
.screen-head h2 { font-size: 1.45rem; font-weight: 750; }

.brand { display: flex; align-items: center; gap: 1rem; padding-top: .6rem; }
.brand-mark {
  display: grid; place-items: center; width: 60px; height: 60px; flex: none; border-radius: 18px;
  background: linear-gradient(140deg, var(--primary), var(--secondary));
  color: #fff; box-shadow: var(--shadow-1), var(--glow);
}
.brand-mark svg { width: 34px; height: 34px; }
.brand h1 { font-size: 2rem; font-weight: 750; }
.tagline { color: var(--text-dim); font-size: .97rem; max-width: 48ch; }

/* The light/dark switch. Both destinations are drawn on the track, so it reads as a
   choice between two things rather than as an unlabelled on/off. */
.theme-toggle {
  margin-left: auto; flex: none; background: none; border: none; padding: 0;
  min-width: var(--tap); min-height: var(--tap);
  display: grid; place-items: center;
}
.theme-toggle-track {
  position: relative; display: flex; align-items: center; gap: .1rem;
  width: 64px; height: 34px; padding: 0 .3rem;
  background: var(--surface-2); border: 1.5px solid var(--border); border-radius: 999px;
  transition: background .2s ease, border-color .2s ease;
}
.theme-toggle-icon { display: grid; place-items: center; width: 28px; height: 28px; z-index: 1; color: var(--text-dim); transition: color .2s ease; }
.theme-toggle-icon svg { width: 17px; height: 17px; }
.theme-toggle-knob {
  position: absolute; top: 2px; left: 2px; width: 27px; height: 27px; border-radius: 50%;
  background: linear-gradient(135deg, var(--primary), var(--secondary));
  box-shadow: var(--shadow-1);
  transition: transform .24s cubic-bezier(.3,.9,.4,1.2);
}
.theme-toggle[aria-checked="true"] .theme-toggle-knob { transform: translateX(30px); }
.theme-toggle[aria-checked="false"] .theme-toggle-icon:first-of-type,
.theme-toggle[aria-checked="true"] .theme-toggle-icon:last-of-type { color: #fff; }

.profile-strip { display: flex; flex-wrap: wrap; gap: .5rem; }
.chip {
  display: inline-flex; align-items: center; gap: .45rem;
  background: var(--surface); border: 1.5px solid var(--border-soft);
  border-radius: 999px; padding: .45rem .85rem; font-size: .9rem; font-weight: 600; color: var(--text-dim);
}
.chip strong { color: var(--text); font-weight: 750; }
.chip svg { width: 17px; height: 17px; opacity: .9; }

/* ------------------------------ The CEFR ladder --------------------------
   Six rungs of one scale. A row of short codes rather than six description
   cards: a scale reads as a scale, and the chosen rung explains itself in full
   underneath instead of every rung explaining itself at once. */
.field-note { color: var(--text-dim); font-size: .88rem; margin-top: -.15rem; }

.cefr-row { display: flex; gap: .4rem; flex-wrap: wrap; }
.cefr-btn {
  flex: 1 1 4.2rem; min-width: 3.6rem;
  min-height: var(--tap); padding: .55rem .3rem;
  display: grid; place-items: center;
  background: var(--surface); color: var(--text-dim);
  border: 1.5px solid var(--border); border-radius: var(--radius-xs);
  font: inherit; cursor: pointer;
  transition: background .16s ease, border-color .16s ease, color .16s ease;
}
.cefr-code { font-family: var(--font-display, inherit); font-size: 1.02rem; font-weight: 750; letter-spacing: .01em; }
.cefr-btn:hover { border-color: color-mix(in srgb, var(--primary) 55%, transparent); color: var(--text); }
.cefr-btn[aria-checked="true"] {
  background: linear-gradient(140deg, var(--primary), var(--secondary));
  border-color: transparent; color: #fff;
  box-shadow: var(--shadow-1);
}
.cefr-blurb { color: var(--text-dim); font-size: .92rem; min-height: 2.4em; }

/* --------------------------------- Menu --------------------------------- */
.menu-grid { display: grid; grid-template-columns: 1fr; gap: .75rem; }
@media (min-width: 640px) { .menu-grid { grid-template-columns: 1fr 1fr; } }

.menu-card {
  display: grid; grid-template-columns: 50px 1fr; grid-template-rows: auto auto;
  gap: .15rem .95rem; text-align: left; min-height: var(--tap);
  background: var(--surface); border: 1.5px solid var(--border-soft);
  border-radius: var(--radius); padding: 1.05rem 1.15rem; color: var(--text);
  transition: transform .12s ease, border-color .16s ease, background .16s ease;
}
.menu-card:hover { border-color: color-mix(in srgb, var(--primary) 55%, transparent); background: var(--surface-2); }
.menu-card:active { transform: scale(.99); }
.menu-icon {
  grid-row: 1 / span 2; display: grid; place-items: center; width: 50px; height: 50px; border-radius: 15px;
  background: color-mix(in srgb, var(--primary) 18%, transparent); color: var(--primary-2);
}
.menu-icon svg { width: 24px; height: 24px; }
.menu-label { font-family: var(--font-display); font-weight: 650; font-size: 1.12rem; }
.menu-desc { color: var(--text-dim); font-size: .9rem; line-height: 1.4; }
.menu-card-primary {
  background: linear-gradient(135deg, color-mix(in srgb, var(--primary) 26%, var(--surface-solid)), var(--surface-solid));
  border-color: color-mix(in srgb, var(--primary) 45%, transparent);
}
.menu-card-primary .menu-icon { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: #fff; }

/* --------------------------------- Setup -------------------------------- */
.field { display: flex; flex-direction: column; gap: .6rem; }
.field h3 { font-family: var(--font); font-size: .84rem; text-transform: uppercase; letter-spacing: .08em; color: var(--text-dim); font-weight: 700; }

.lang-pair { display: grid; grid-template-columns: 1fr auto 1fr; align-items: end; gap: .8rem; }
.lang-arrow { color: var(--text-dim); padding-bottom: .7rem; }
.lang-arrow svg { width: 20px; height: 20px; }
@media (max-width: 540px) { .lang-pair { grid-template-columns: 1fr; } .lang-arrow { display: none; } }

.pill-row { display: flex; flex-wrap: wrap; gap: .5rem; }
.pill {
  min-height: var(--tap);
  background: var(--surface); border: 1.5px solid var(--border); color: var(--text-dim);
  border-radius: 999px; padding: .55rem 1.1rem; font-weight: 650; font-size: .96rem;
  transition: all .15s ease;
}
.pill[aria-checked="true"] {
  background: color-mix(in srgb, var(--primary) 24%, transparent);
  border-color: var(--primary); color: var(--text);
  /* Selection is never colour alone: the chosen pill also gains weight and a ring. */
  box-shadow: inset 0 0 0 1.5px var(--primary);
}

.choice-grid { display: grid; grid-template-columns: 1fr; gap: .6rem; }
@media (min-width: 580px) { .choice-grid { grid-template-columns: 1fr 1fr; } .choice-grid-3 { grid-template-columns: repeat(3, 1fr); } }
.choice {
  display: grid; grid-template-columns: 40px 1fr; gap: .2rem .8rem; text-align: left;
  background: var(--surface); border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  padding: .9rem 1rem; color: var(--text); transition: all .15s ease; position: relative;
}
.choice-icon { grid-row: 1 / span 2; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 12px; background: var(--surface-2); color: var(--text-dim); }
.choice-icon svg { width: 21px; height: 21px; }
.choice-name { font-weight: 700; font-size: 1rem; }
.choice-desc { color: var(--text-dim); font-size: .86rem; line-height: 1.35; }
.choice[aria-checked="true"] { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 14%, var(--surface-solid)); }
.choice[aria-checked="true"] .choice-icon { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: #fff; }
/* The tick is the non-colour half of the signal. */
.choice[aria-checked="true"]::after {
  content: "✓"; position: absolute; top: .55rem; right: .8rem;
  font-weight: 800; color: var(--primary-2); font-size: 1rem;
}
.warn { color: var(--highlight); font-size: .9rem; font-weight: 650; display: flex; align-items: center; gap: .4rem; }

/* ---------------------------------- Play -------------------------------- */
.play { display: flex; flex-direction: column; height: 100dvh; overflow: hidden; }

.topbar {
  display: flex; align-items: center; gap: var(--space-2); flex: none;
  padding: max(var(--space-2), env(safe-area-inset-top)) var(--gutter) var(--space-2);
}
.topbar-actions { display: flex; gap: .45rem; }

/* ---------------------------------------------------------------------------
   The objective.

   Its own row, full width, at every size. The rules it obeys, in order of how often
   they get broken by a later tweak:

     · IT IS NEVER TRUNCATED. No nowrap, no ellipsis, no fixed height. A long goal
       ("Colete 5 verbos no presente contínuo") wraps to two or three lines and the
       board gives up the pixels, because half a goal is worse than a smaller maze.
     · THE ICON IS BIG ENOUGH TO BE THE FIRST THING SEEN — 44px on a desktop, 38 on a
       phone, in a tinted tile. It is the entry point of the hierarchy
       icon → objective → progress, and at 18px it was not.
     · TYPE SCALES WITH THE VIEWPORT rather than stepping at breakpoints, so there is
       no width at which it is briefly too small to read.
   --------------------------------------------------------------------------- */
.objective {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "icon body" "prog prog";
  align-items: center; gap: .35rem .8rem;
  margin: 0 var(--gutter) var(--space-2); padding: var(--space-3); flex: none;
  background: var(--surface); border: 1.5px solid var(--border-soft);
  border-radius: var(--radius-sm);
}
.objective-icon {
  grid-area: icon; display: grid; place-items: center;
  width: 38px; height: 38px; flex: none; border-radius: 12px;
  background: color-mix(in srgb, var(--secondary) 18%, transparent); color: var(--secondary);
}
.objective-icon svg { width: 23px; height: 23px; }
.objective-body { grid-area: body; min-width: 0; }
.objective-text {
  font-weight: 700; font-size: clamp(.98rem, 3.4vw, 1.14rem); line-height: 1.25;
  /* The whole point: wrap, never clip. `anywhere` covers a single word longer than the
     column, which no ellipsis rule would have handled either. */
  overflow-wrap: anywhere; white-space: normal;
}
.objective-prompt {
  color: var(--primary-2); font-size: clamp(.88rem, 3vw, 1rem); font-weight: 600;
  margin-top: .2rem; line-height: 1.3; overflow-wrap: anywhere; white-space: normal;
}
.objective-prompt:empty { display: none; }

/* The grammar note. It is the whole reason the game exists, so on the bands that are
   about learning it gets its own tinted line; on the harder ones it shrinks to the
   tense name alone, and it can be switched off entirely. */
.objective-tense {
  display: inline-flex; align-items: baseline; gap: .45rem; flex-wrap: wrap;
  margin-top: .35rem; padding: .25rem .6rem; border-radius: 999px;
  background: color-mix(in srgb, var(--primary) 14%, transparent);
  font-size: clamp(.82rem, 2.7vw, .94rem); line-height: 1.35; overflow-wrap: anywhere;
}
.objective-tense:empty { display: none; }
.objective-tense b { color: var(--primary-2); font-weight: 700; }
.objective-tense span { color: var(--text-dim); }
.objective-tense.is-brief { background: none; padding: 0; }
.objective-progress {
  grid-area: prog; display: flex; align-items: center; gap: .6rem; min-width: 0;
}
.objective-count {
  font-family: var(--font-display); font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 1.05rem; color: var(--secondary); flex: none;
}
.objective-bar { flex: 1; height: 8px; }
.objective-bar > i { background: linear-gradient(90deg, var(--secondary), var(--primary-2)); transition: width .25s ease; }

/* From tablet up there is room for one row: icon, goal, progress side by side. */
@media (min-width: 700px) {
  .objective {
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "icon body prog";
    gap: .9rem; padding: .75rem 1rem;
  }
  .objective-icon { width: 44px; height: 44px; border-radius: 14px; }
  .objective-icon svg { width: 26px; height: 26px; }
  .objective-progress { flex: none; width: 190px; }
}

/* Landscape phones are short, not narrow: the row fits, the type comes down a step,
   and the goal still wraps rather than clipping. */
@media (orientation: landscape) and (max-height: 620px) {
  .objective {
    grid-template-columns: auto 1fr auto;
    grid-template-areas: "icon body prog";
    margin-bottom: .35rem; padding: .45rem .7rem; gap: .6rem;
  }
  .objective-icon { width: 32px; height: 32px; border-radius: 10px; }
  .objective-icon svg { width: 20px; height: 20px; }
  .objective-text { font-size: .95rem; }
  .objective-prompt { font-size: .86rem; }
  .objective-progress { flex: none; width: 130px; }
}

.play-title {
  flex: 1; min-width: 0; text-align: center; font-weight: 650; font-size: .95rem;
  color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.hud {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; flex: none;
  padding: 0 var(--gutter) var(--space-2);
}
.hud-item { display: inline-flex; align-items: center; gap: .35rem; font-variant-numeric: tabular-nums; font-weight: 700; font-size: .95rem; color: var(--text-dim); }
.hud-icon { display: inline-grid; place-items: center; }
.hud-icon svg { width: 18px; height: 18px; }
.hud-value { color: var(--text); font-family: var(--font-display); }
.hud-combo { color: var(--highlight); }
.hud-combo .hud-value { color: var(--highlight); }
.hud-lives { display: inline-flex; gap: .22rem; }
.hud-lives svg { width: 19px; height: 19px; color: var(--danger); }
.hud-lives .spent { opacity: .25; }

/* The mistake counter. It changes colour AND gains a border AND, at the last chance,
   a word — three channels, because this is the signal that ends the run. */
.hud-mistakes { padding: .1rem .5rem; border-radius: 999px; border: 1.5px solid transparent; }
.hud-mistakes.is-near { color: var(--highlight); border-color: var(--highlight); }
.hud-mistakes.is-near .hud-value { color: var(--highlight); }
.hud-mistakes.is-last { color: var(--danger); border-color: var(--danger); background: color-mix(in srgb, var(--danger) 15%, transparent); }
.hud-mistakes.is-last .hud-value { color: var(--danger); }

.hud-effects { display: inline-flex; gap: .35rem; margin-left: auto; flex-wrap: wrap; }
.effect-chip {
  display: inline-flex; align-items: center; gap: .3rem;
  background: color-mix(in srgb, var(--secondary) 20%, transparent);
  border: 1.5px solid color-mix(in srgb, var(--secondary) 50%, transparent);
  color: var(--secondary); border-radius: 999px; padding: .16rem .6rem; font-size: .8rem; font-weight: 700;
}
.effect-chip svg { width: 14px; height: 14px; }

.stage {
  flex: 1; min-height: 0; display: flex; flex-direction: column;
  align-items: stretch; justify-content: center;
  gap: var(--space-2); padding: 0 var(--space-2) var(--space-2);
}
.play-col { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-2); min-height: 0; min-width: 0; flex: 1 1 auto; }
.play-side { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); flex: none; }
/* The board's hole. It is a flex child that takes whatever the strip and the pad leave,
   and fitCanvas measures it rather than recomputing it — see fitCanvas() in app.js. The
   zero minimums are what let it shrink below the canvas it contains, which is what stops
   the two from arguing. */
.canvas-wrap {
  position: relative; line-height: 0;
  flex: 1 1 auto; min-width: 0; min-height: 0;
  display: grid; place-items: center;
}
#maze { display: block; border-radius: 18px; box-shadow: var(--shadow-2); touch-action: none; }

/* ---------------------------------------------------------------------------
   The learning strip.

   Reserved space above the board, never a panel on top of it. Three things follow
   from that and all three are the point:
     · nothing the player needs to see is ever covered — not the character, not a
       shard, not a word, not a touch control;
     · the maze never resizes when a word is collected, because the space was always
       there. A board that jumps mid-run is unplayable;
     · the height is set once per run from the difficulty band, since the band decides
       how much the card says. It is a CSS variable so the canvas fit can read it.
   --------------------------------------------------------------------------- */
.learn-strip {
  width: min(100%, 520px); min-height: var(--strip-h, 84px); height: var(--strip-h, 84px);
  display: flex; flex-direction: column; justify-content: center;
  background: var(--surface); border: 1.5px solid var(--border-soft);
  border-left-width: 5px; border-left-color: transparent;
  border-radius: var(--radius-sm); padding: var(--space-2) var(--space-3);
  text-align: left; line-height: 1.3; overflow: hidden;
  transition: border-color .18s ease, background .18s ease;
}
.learn-strip:empty::after {
  /* An empty strip still has to look intentional rather than like a broken panel. */
  content: ""; display: block; height: 2px; width: 44px;
  background: var(--border); border-radius: 2px; opacity: .5;
}
.learn-strip.is-good { border-left-color: var(--ok); }
.learn-strip.is-wrong { border-left-color: var(--danger); background: color-mix(in srgb, var(--danger) 8%, var(--surface)); }
.learn-strip.is-banner { border-left-color: var(--primary-2); background: color-mix(in srgb, var(--primary) 12%, var(--surface)); }
.learn-strip.is-ready { border-left-color: var(--secondary); }

.learn-top { display: flex; align-items: center; gap: .5rem; flex-wrap: nowrap; min-width: 0; }
.learn-mark { display: grid; place-items: center; width: 24px; height: 24px; border-radius: 8px; flex: none; }
.learn-mark svg { width: 16px; height: 16px; }
.learn-strip.is-good .learn-mark { background: color-mix(in srgb, var(--ok) 20%, transparent); color: var(--ok); }
.learn-strip.is-wrong .learn-mark { background: color-mix(in srgb, var(--danger) 20%, transparent); color: var(--danger); }
.learn-strip.is-banner .learn-mark,
.learn-strip.is-ready .learn-mark { background: color-mix(in srgb, var(--primary) 20%, transparent); color: var(--primary-2); }
.learn-word { font-family: var(--font-display); font-weight: 700; font-size: 1.1rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.learn-ipa { color: var(--text-dim); font-size: .84rem; white-space: nowrap; }
.learn-tag {
  margin-left: auto; flex: none; font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--primary-2); background: color-mix(in srgb, var(--primary) 20%, transparent);
  border-radius: 999px; padding: .14rem .5rem;
}
.learn-translation { font-size: .96rem; font-weight: 650; margin-top: .12rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.learn-strip.is-good .learn-translation { color: var(--ok); }
.learn-strip.is-wrong .learn-translation { color: var(--danger); }
.learn-sentence, .learn-note { color: var(--text-dim); font-size: .84rem; margin-top: .14rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.learn-note strong { color: var(--text); }

/* ---------------------------------------------------------------------------
   The countdown card.

   What is left of a much bigger thing. It used to carry a "Preparation" heading, the
   level goal, a full controls legend and a skip button, laid out on a five-area grid
   that rearranged itself three times across breakpoints — a second How to play, drawn
   over the board it was describing, immediately after the player had read the first
   one. All of that moved in front of the match, into the brief. What is over the board
   now is the number and nothing else, so it needs no grid at all.

   Interaction discipline is unchanged and still matters: the card is pointer-transparent
   so a player who starts swiping during the count still steers.
   --------------------------------------------------------------------------- */
.count-card {
  position: absolute; left: 50%; transform: translateX(-50%);
  pointer-events: none; z-index: 2;
  display: grid; place-items: center;
  min-width: clamp(84px, 22%, 132px);
  background: color-mix(in srgb, var(--surface-solid) 92%, transparent);
  border: 1.5px solid var(--border); border-radius: var(--radius);
  padding: var(--space-2) var(--space-3);
  box-shadow: var(--shadow-2); text-align: center;
}
/* Kept clear of the board's edge by a real gap that scales with the board. */
.count-card.is-top { top: clamp(10px, 6%, 30px); }
.count-card.is-bottom { bottom: clamp(10px, 6%, 30px); }

.ready-count {
  font-family: var(--font-display); font-size: clamp(2.8rem, 11vw, 4.2rem);
  font-weight: 750; line-height: 1; color: var(--text);
}
.ready-count.is-go { font-size: clamp(1.9rem, 7.5vw, 2.6rem); color: var(--secondary); letter-spacing: .04em; }
.ready-count.is-waiting { font-size: clamp(1.1rem, 4.2vw, 1.35rem); color: var(--text-dim); font-weight: 650; letter-spacing: .02em; }

/* ---------------------------------------------------------------------------
   The pause veil.

   It used to live inside the canvas wrapper, which meant it darkened the board and
   nothing else: the HUD stayed bright, the touch pad stayed bright, the margins stayed
   bright, and pausing read as something that had happened to the maze rather than to
   the match. It now sits over `.play-area` — everything from the HUD down — so the
   whole of the gameplay is under one layer.

   The objective row is deliberately OUTSIDE that region and stays legible: the one
   thing worth reading while stopped is what you were trying to do.

   Translucent rather than opaque, so the board is visibly still there and visibly
   frozen. One layer only; stacking a second is what made the old pause feel heavy.
   --------------------------------------------------------------------------- */
.play-area { position: relative; display: flex; flex-direction: column; flex: 1; min-height: 0; }

.overlay {
  position: absolute; inset: 0; z-index: 4;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(5px);
  text-align: center; padding: 1.2rem;
  animation: veil .16s ease;
}
@keyframes veil { from { opacity: 0; } to { opacity: 1; } }

/* The card is what the eye should land on, so it is a surface rather than loose text. */
.overlay-inner {
  background: var(--surface-solid);
  border: 1.5px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2);
  padding: 1.3rem 1.5rem;
  max-width: min(420px, 92%);
}
.overlay-title { font-family: var(--font-display); font-size: 1.8rem; font-weight: 750; }
.overlay-sub { color: var(--text-dim); font-size: .98rem; margin-top: .4rem; max-width: 36ch; }
.overlay-actions { display: flex; gap: .55rem; justify-content: center; flex-wrap: wrap; margin-top: 1rem; }
.overlay-actions .btn { flex: 1 1 8rem; }

@media (prefers-reduced-motion: reduce) { .overlay { animation: none; } }
[data-motion="reduce"] .overlay { animation: none; }

/* Also the "say it again" control: it sits outside the board, so making it tappable
   costs no swipe area. */
.subtitle {
  flex: none; max-width: min(700px, 94vw); min-height: 38px; text-align: center;
  background: var(--surface); border: 1.5px solid var(--border-soft); border-radius: 999px;
  padding: .4rem 1.1rem; font-size: .92rem; color: var(--text-dim);
  font-family: inherit; transition: background .16s ease, color .16s ease;
}
.subtitle:hover { background: var(--surface-2); color: var(--text); }
.subtitle:active { transform: scale(.98); }

/* On-screen pad: thumb-sized, out of the board's way. */
.pad { flex: none; position: relative; width: var(--pad-size); height: calc(var(--pad-size) * .66); margin-bottom: env(safe-area-inset-bottom); }
.pad-btn {
  position: absolute; width: 32%; height: 46%; min-width: var(--tap); min-height: var(--tap);
  display: grid; place-items: center;
  background: var(--surface-solid); color: var(--text);
  border: 1.5px solid var(--border); border-radius: var(--radius-xs);
  box-shadow: var(--shadow-1); touch-action: none;
}
.pad-btn svg { width: 24px; height: 24px; }
.pad-btn.is-active { background: var(--primary); color: #fff; border-color: transparent; }
.pad-up { left: 34%; top: 0; }
.pad-down { left: 34%; bottom: 0; }
.pad-left { left: 0; top: 27%; }
.pad-right { right: 0; top: 27%; }

/* Landscape phones. The board is the game, and in landscape it was getting about a
   quarter of the screen — so everything around it is compacted rather than the board
   being asked to fit in what is left. The strip moves BESIDE the maze (it is reserved
   space, not a floating card, so it can sit anywhere as long as it never covers the
   board), the top bar and the objective shrink to one compact line each, and the pad
   tucks in. Nothing is hidden: the objective still wraps and is still read aloud. */
@media (orientation: landscape) and (max-height: 620px) {
  .stage { flex-direction: row; gap: .5rem; align-items: stretch; padding: 0 .4rem .4rem; }
  .play-col { flex: 1 1 auto; min-width: 0; }
  .play-side { justify-content: center; flex: none; }
  .pad { --pad-size: 124px; margin-bottom: 0; }

  .learn-strip { --strip-h: 100%; width: 150px; flex: none; height: auto; align-self: stretch; }
  .play-col { flex-direction: row; align-items: stretch; gap: .5rem; }

  .topbar { padding-top: .2rem; padding-bottom: .2rem; }
  .play-title { font-size: .82rem; }
  .topbar .icon-btn { min-width: 38px; min-height: 38px; }

  /* The objective keeps its words and gives up its padding. */
  .objective { padding: .3rem .55rem; gap: .5rem; }
  .objective-icon { width: 24px; height: 24px; }
  .objective-icon svg { width: 16px; height: 16px; }
  .objective-text { font-size: .84rem; }
  /* The example sentence and the tense note are the two lines that make this a block
     rather than a line; landscape drops them and keeps the goal itself. */
  .objective-prompt, .objective-tense { display: none; }
  /* One line, not a stacked block: the goal, then the count, side by side. */
  .objective { flex-wrap: nowrap; align-items: center; }
  .objective-body { display: flex; align-items: center; gap: .5rem; min-width: 0; }
  .objective-body > * { margin: 0; }
  .objective-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .objective-progress { flex: none; width: 110px; }

  .hud { padding-bottom: .15rem; gap: .45rem; font-size: .8rem; }
  .hud-item { padding: .1rem .35rem; }
}

/* Wide screens: the board was height-bound while ~860px of width sat unused, because
   the stage stacked strip-over-maze at every size. Past this width the strip moves to
   the side, which hands its whole height back to the board.

   The height threshold is 560, not 700: a 1366×768 laptop reports an innerHeight around
   625 once the browser's own chrome is out, and gating on 700 meant the machine this was
   written for never took the branch. Viewport height is what CSS sees; window height is
   not. */
@media (min-width: 1100px) and (min-height: 560px) {
  /* stretch, not center: a centred flex item is sized by its content, and the content
     here is the canvas whose size we are trying to decide. That circle collapsed the
     wrapper to the minimum and handed a 1920px desktop a 152px board. */
  .play-col { flex-direction: row; align-items: stretch; gap: .75rem; }
  .learn-strip { --strip-h: 100%; width: 300px; flex: none; height: auto; align-self: stretch; min-height: 140px; }
  .canvas-wrap { flex: 1 1 auto; display: grid; place-items: center; }
}

/* Very short viewports: the strip gives up a line before the board gives up size. */
@media (max-height: 700px) and (orientation: portrait) {
  .learn-strip { --strip-h: 70px; }
}

/* --------------------------------- Modals ------------------------------- */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center; padding: 1rem;
  background: color-mix(in srgb, var(--bg) 76%, transparent);
  backdrop-filter: blur(8px);
  animation: fade .18s ease;
}
.modal {
  width: min(540px, 100%); max-height: min(90dvh, 820px); overflow: auto;
  background: var(--surface-solid); border: 1.5px solid var(--border);
  border-radius: var(--radius); box-shadow: var(--shadow-2);
  padding: 1.2rem 1.25rem calc(1.2rem + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: .95rem;
  animation: pop .2s cubic-bezier(.2,.9,.3,1.1);
}
.modal-wide { width: min(760px, 100%); }

/* ---------------------------- How to play --------------------------------
   Deliberately bigger than the other modals. The card this replaces was a
   ~300px panel sharing the board with a countdown, an objective row, a HUD and
   sometimes a pause veil; everything in it was abbreviated because there was no
   room. There is room here — it opens over the setup screen, with no match
   behind it — so the brief can be read rather than skimmed.

   Two columns where the viewport allows, one where it does not. The switch is
   on available width, not on a device guess. */
.modal-howto { width: min(920px, 100%); max-height: min(92dvh, 900px); }
.howto-grid { display: grid; grid-template-columns: 1fr; gap: 1.1rem; }
@media (min-width: 760px) { .howto-grid { grid-template-columns: 1.15fr 1fr; gap: 1.6rem; } }

.howto-col { display: flex; flex-direction: column; gap: .7rem; min-width: 0; }
.howto-h {
  display: flex; align-items: center; gap: .5rem;
  font-size: .82rem; font-weight: 750; letter-spacing: .08em; text-transform: uppercase;
  color: var(--primary-2);
}
.howto-h svg { width: 17px; height: 17px; }
.howto-mode {
  background: color-mix(in srgb, var(--primary) 10%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--primary) 30%, transparent);
  border-radius: var(--radius-xs); padding: .6rem .75rem;
  font-size: .93rem; line-height: 1.45;
}
.howto-mode strong { font-weight: 750; }

.howto-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: .6rem; }
.howto-list li { display: flex; gap: .6rem; align-items: flex-start; font-size: .94rem; line-height: 1.5; color: var(--text); }
/* A shape, not a colour: the list still reads as a list with colour removed. */
.howto-dot {
  flex: none; width: 7px; height: 7px; margin-top: .52em; border-radius: 2px;
  background: var(--secondary); transform: rotate(45deg);
}

.howto-controls { display: flex; flex-direction: column; gap: .55rem; }
.howto-keys { display: grid; grid-template-columns: repeat(3, 46px); gap: .3rem; justify-content: center; margin-bottom: .2rem; }
.howto-key {
  display: grid; place-items: center; gap: .05rem;
  height: 46px; border-radius: var(--radius-xs);
  background: var(--surface-2); border: 1.5px solid var(--border);
  font-size: .76rem; font-weight: 750; color: var(--text);
}
.howto-key svg { width: 15px; height: 15px; }
.howto-key.is-blank { background: none; border-color: transparent; }
.howto-ctrl-line { display: flex; align-items: center; gap: .5rem; font-size: .92rem; color: var(--text-dim); }
.howto-ctrl-line svg { width: 17px; height: 17px; flex: none; color: var(--secondary); }

/* -------------------------- The help centre -----------------------------
   One modal, with the mode list as navigation inside it. On a wide screen the list
   is a column beside the content; on a narrow one it becomes a scrolling strip of
   tabs above it, because two columns at 360px are two columns of nothing. */
.modal-help { width: min(960px, 100%); max-height: min(92dvh, 940px); }

.help-general { display: flex; flex-direction: column; gap: .7rem; }
.help-general-grid { display: grid; grid-template-columns: 1fr; gap: 1rem; }
@media (min-width: 720px) { .help-general-grid { grid-template-columns: 1.3fr 1fr; gap: 1.6rem; } }

.help-modes { display: flex; flex-direction: column; gap: .7rem; }
.help-split { display: grid; grid-template-columns: 1fr; gap: .8rem; }
@media (min-width: 720px) { .help-split { grid-template-columns: 200px 1fr; gap: 1.2rem; align-items: start; } }

.help-tabs {
  display: flex; gap: .35rem;
  overflow-x: auto; overflow-y: hidden;
  padding-bottom: .3rem;               /* room for the scrollbar, not for the shadow */
  scrollbar-width: thin;
}
@media (min-width: 720px) {
  .help-tabs { flex-direction: column; overflow: visible; padding-bottom: 0; }
}
.help-tab {
  display: inline-flex; align-items: center; gap: .45rem; flex: none;
  min-height: var(--tap); padding: .5rem .75rem;
  background: var(--surface); color: var(--text-dim);
  border: 1.5px solid var(--border-soft); border-radius: var(--radius-xs);
  font: inherit; font-size: .9rem; font-weight: 650; text-align: left;
  white-space: nowrap; cursor: pointer;
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
@media (min-width: 720px) { .help-tab { width: 100%; white-space: normal; } }
.help-tab-icon svg { width: 17px; height: 17px; flex: none; }
.help-tab:hover { color: var(--text); border-color: color-mix(in srgb, var(--primary) 45%, transparent); }
/* Selected by weight, border and a bar — never by colour alone. */
.help-tab[aria-selected="true"] {
  background: color-mix(in srgb, var(--primary) 14%, var(--surface));
  border-color: var(--primary);
  color: var(--text); font-weight: 750;
  box-shadow: inset 3px 0 0 var(--primary);
}

.help-panel { min-width: 0; display: flex; flex-direction: column; gap: .8rem; }
.help-panel:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; border-radius: var(--radius-xs); }
.help-mode-title { font-family: var(--font-display); font-size: 1.25rem; font-weight: 750; }
.help-mode-short {
  color: var(--text-dim); font-size: .95rem; margin-top: -.5rem;
  padding-bottom: .5rem; border-bottom: 1.5px solid var(--border-soft);
}
.help-row { display: flex; flex-direction: column; gap: .18rem; }
.help-row-h {
  font-size: .74rem; font-weight: 750; letter-spacing: .07em;
  text-transform: uppercase; color: var(--primary-2);
}
.help-row p { font-size: .94rem; line-height: 1.5; }
.help-tip {
  display: flex; gap: .5rem; align-items: flex-start;
  background: color-mix(in srgb, var(--secondary) 12%, var(--surface));
  border: 1.5px solid color-mix(in srgb, var(--secondary) 34%, transparent);
  border-radius: var(--radius-xs); padding: .6rem .75rem;
  font-size: .92rem; line-height: 1.5;
}

/* The tutorial preference. Visually apart from the mode list, because it is a setting
   and not a topic. */
.help-pref {
  border-top: 1.5px solid var(--border-soft);
  padding-top: .9rem;
}
.switch-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; cursor: pointer; min-height: var(--tap); }
.switch-text { font-size: .95rem; font-weight: 650; }
.switch-input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-ui {
  position: relative; flex: none; width: 62px; height: 32px; border-radius: 999px;
  background: var(--surface-2); border: 1.5px solid var(--border);
  display: grid; align-items: center;
  transition: background .18s ease, border-color .18s ease;
}
.switch-ui::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 24px; height: 24px;
  border-radius: 50%; background: var(--text-dim);
  transition: transform .2s cubic-bezier(.3,.9,.4,1.2), background .18s ease;
}
/* The word, not just the knob — a switch that only changes colour says nothing. */
.switch-word::before { content: "OFF"; position: absolute; right: .55rem; font-size: .62rem; font-weight: 800; letter-spacing: .06em; color: var(--text-dim); }
.switch-input:checked + .switch-ui { background: color-mix(in srgb, var(--primary) 26%, transparent); border-color: var(--primary); }
.switch-input:checked + .switch-ui::after { transform: translateX(30px); background: var(--primary); }
.switch-input:checked + .switch-ui .switch-word::before { content: "ON"; left: .6rem; right: auto; color: var(--primary-2); }
.switch-input:focus-visible + .switch-ui { outline: 2px solid var(--primary); outline-offset: 2px; }

/* "Do not show this again", in the pre-match brief. */
.howto-skip-row {
  display: flex; align-items: center; gap: .6rem; cursor: pointer;
  min-height: var(--tap); font-size: .92rem; color: var(--text-dim);
  border-top: 1.5px solid var(--border-soft); padding-top: .8rem;
}
.howto-skip-row input { width: 20px; height: 20px; flex: none; accent-color: var(--primary); cursor: pointer; }

@media (prefers-reduced-motion: reduce) { .switch-ui, .switch-ui::after { transition: none; } }
[data-motion="reduce"] .switch-ui, [data-motion="reduce"] .switch-ui::after { transition: none; }

.help-tip svg { width: 16px; height: 16px; flex: none; color: var(--secondary); }
.modal-howto .modal-actions { margin-top: .3rem; }
.modal-howto .modal-actions .btn { flex: 1 1 10rem; }

/* The modal animates in; a reader who asked for less motion gets none. */
@media (prefers-reduced-motion: reduce) {
  .modal, .modal-backdrop { animation: none; }
}
[data-motion="reduce"] .modal,
[data-motion="reduce"] .modal-backdrop { animation: none; }

.modal-head { display: flex; align-items: center; justify-content: space-between; gap: .8rem; }
.modal-head h2 { font-size: 1.32rem; font-weight: 750; }
.modal-body { display: flex; flex-direction: column; gap: .95rem; }
.modal-actions { display: flex; flex-wrap: wrap; gap: .55rem; }
.modal-actions .btn { flex: 1 1 auto; }
.modal-actions:empty { display: none; }
.modal-result h2 { font-size: 1.6rem; font-weight: 750; text-align: center; }
.result-best { display: flex; align-items: center; justify-content: center; gap: .45rem; color: var(--highlight); font-weight: 700; font-size: .95rem; }
.result-best svg { width: 18px; height: 18px; }

.result-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .55rem; }
@media (min-width: 500px) { .result-grid { grid-template-columns: repeat(3, 1fr); } }
.stat { background: var(--surface-2); border-radius: var(--radius-sm); padding: .7rem .8rem; display: flex; flex-direction: column; gap: .12rem; }
.stat-label { font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); font-weight: 700; }
.stat-value { font-family: var(--font-display); font-size: 1.32rem; font-weight: 750; font-variant-numeric: tabular-nums; }
.stat-hi .stat-value { color: var(--secondary); }

/* The educational post-mortem: what actually went wrong, named. */
.debrief { background: color-mix(in srgb, var(--highlight) 12%, var(--surface-2)); border: 1.5px solid color-mix(in srgb, var(--highlight) 40%, transparent); border-radius: var(--radius-sm); padding: .85rem .95rem; display: flex; flex-direction: column; gap: .5rem; }
.debrief-title { display: flex; align-items: center; gap: .45rem; font-weight: 750; font-size: 1rem; }
.debrief-title svg { width: 19px; height: 19px; color: var(--highlight); }
.debrief-list { display: flex; flex-direction: column; gap: .3rem; }
.debrief-item { display: flex; align-items: baseline; gap: .5rem; font-size: .92rem; }
.debrief-item b { font-weight: 700; }
.debrief-item span { color: var(--text-dim); font-size: .85rem; margin-left: auto; font-variant-numeric: tabular-nums; }

.achievement-row { display: flex; flex-wrap: wrap; gap: .45rem; }
.achievement {
  display: inline-flex; align-items: center; gap: .4rem;
  background: color-mix(in srgb, var(--highlight) 16%, transparent);
  border: 1.5px solid color-mix(in srgb, var(--highlight) 45%, transparent);
  color: var(--highlight); border-radius: 999px; padding: .32rem .75rem; font-size: .84rem; font-weight: 700;
}
.achievement svg { width: 16px; height: 16px; }

/* ------------------------------ Lists / tables --------------------------- */
.section-title { font-size: .84rem; text-transform: uppercase; letter-spacing: .08em; color: var(--text-dim); font-weight: 700; }
.hint { color: var(--text-dim); font-size: .9rem; line-height: 1.45; display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.hint svg { width: 17px; height: 17px; flex: none; }
.empty { color: var(--text-dim); font-size: .95rem; text-align: center; padding: 1.6rem 0; }

.word-table th { text-align: left; font-size: .76rem; text-transform: uppercase; letter-spacing: .05em; color: var(--text-dim); padding: .35rem .55rem; font-weight: 700; }
.word-table td { padding: .55rem .55rem; border-top: 1.5px solid var(--border-soft); font-size: .96rem; vertical-align: middle; }
.word-row { cursor: pointer; }
.word-row:hover { background: var(--surface-2); }
.word-term { font-weight: 700; }
.word-sub { color: var(--text-dim); font-size: .82rem; }
.word-class { color: var(--text-dim); font-size: .85rem; white-space: nowrap; }
.hard-btn { min-height: 36px; border: 1.5px solid var(--border); background: transparent; color: var(--text-dim); border-radius: 999px; padding: .25rem .75rem; font-size: .8rem; font-weight: 700; }
.hard-btn[aria-pressed="true"] { background: color-mix(in srgb, var(--danger) 18%, transparent); border-color: var(--danger); color: var(--danger); }
.hard-btn[aria-pressed="true"]::before { content: "✓ "; }

/* Strength bars. The pattern overlay carries the same information as the colour, so a
   weak bar is distinguishable from a strong one without seeing either hue. */
.bar { height: 9px; border-radius: 99px; background: var(--surface-3); overflow: hidden; }
.bar > i { display: block; height: 100%; border-radius: 99px; background: linear-gradient(90deg, var(--primary), var(--primary-2)); }
.bar.is-weak > i {
  background: repeating-linear-gradient(45deg, var(--danger) 0 5px, color-mix(in srgb, var(--danger) 55%, transparent) 5px 10px);
}
.bar.is-strong > i { background: linear-gradient(90deg, var(--secondary), color-mix(in srgb, var(--secondary) 55%, #fff)); }

.gram-row { display: grid; grid-template-columns: 1fr auto; gap: .25rem .7rem; padding: .65rem 0; border-top: 1.5px solid var(--border-soft); }
.gram-name { font-weight: 700; font-size: .98rem; display: flex; align-items: center; gap: .4rem; }
.gram-score { font-size: .85rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.gram-bar { grid-column: 1 / -1; }
.gram-example { grid-column: 1 / -1; color: var(--text-dim); font-size: .86rem; }

/* Verdict flags — the shape-and-word half of every good/bad signal. */
.flag { display: inline-flex; align-items: center; gap: .3rem; font-size: .8rem; font-weight: 700; border-radius: 999px; padding: .1rem .55rem; border: 1.5px solid transparent; }
.flag svg { width: 14px; height: 14px; }
.flag-good { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); background: color-mix(in srgb, var(--ok) 14%, transparent); }
.flag-bad { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.flag-warn { color: var(--highlight); border-color: color-mix(in srgb, var(--highlight) 45%, transparent); background: color-mix(in srgb, var(--highlight) 14%, transparent); }

/* -------------------------------- Settings ------------------------------- */
.set-group { display: flex; flex-direction: column; gap: .75rem; }
.set-row { display: flex; align-items: center; justify-content: space-between; gap: .9rem; min-height: var(--tap); }
.set-row-col { flex-direction: column; align-items: stretch; gap: .4rem; }
/* An icon beside a settings label sits on the text baseline at text size, with one
   gap between it and the words — so the rows that have one line up with the rows that
   do not, instead of looking like a different kind of row. */
.set-label {
  display: inline-flex; align-items: center; gap: .5rem;
  font-weight: 650; font-size: .98rem;
}
.set-label svg { width: 1.15em; height: 1.15em; flex: none; color: var(--text-dim); }
.set-desc { color: var(--text-dim); font-size: .84rem; line-height: 1.35; margin-top: .12rem; }
.switch { position: relative; width: 64px; height: 34px; flex: none; border-radius: 99px; border: 1.5px solid var(--border); background: var(--surface-2); transition: background .16s ease; }
.switch::after { content: ""; position: absolute; top: 2px; left: 2px; width: 26px; height: 26px; border-radius: 50%; background: var(--text-dim); transition: transform .16s ease, background .16s ease; }
.switch[aria-checked="true"] { background: var(--primary); border-color: transparent; }
.switch[aria-checked="true"]::after { transform: translateX(32px); background: #fff; }
/* The switch states its position in words as well, so it is not a colour-and-position
   puzzle. The label sits INSIDE the track, on the side the knob is not — which needs no
   :has() support and no extra room in the row. */
.switch::before {
  content: "OFF"; position: absolute; right: 9px; top: 50%; transform: translateY(-50%);
  font-size: .62rem; font-weight: 800; letter-spacing: .05em; color: var(--text-dim);
}
.switch[aria-checked="true"]::before { content: "ON"; right: auto; left: 11px; color: #fff; }

.seg { display: inline-flex; gap: .25rem; background: var(--surface-2); border-radius: 12px; padding: .25rem; }
.seg button { min-height: 38px; border: none; background: transparent; color: var(--text-dim); border-radius: 9px; padding: .4rem .75rem; font-size: .88rem; font-weight: 650; }
.seg button[aria-checked="true"] { background: var(--surface-solid); color: var(--text); box-shadow: var(--shadow-1); outline: 1.5px solid var(--primary); }
input[type="range"] { width: 100%; height: var(--tap); accent-color: var(--primary); }
select { min-height: var(--tap); background: var(--surface-2); color: var(--text); border: 1.5px solid var(--border); border-radius: 12px; padding: .5rem .7rem; font-family: inherit; font-size: .92rem; max-width: 60%; }

/* --------------------------------- Quiz ---------------------------------- */
.quiz-q { font-family: var(--font-display); font-size: 1.2rem; font-weight: 700; text-align: center; }
.quiz-sub { text-align: center; color: var(--text-dim); font-size: .9rem; }
.quiz-options { display: grid; gap: .5rem; }
.quiz-opt { text-align: left; justify-content: flex-start; padding: .8rem .95rem; font-weight: 650; }
.quiz-opt.is-right { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 16%, transparent); }
.quiz-opt.is-right::before { content: "✓"; font-weight: 800; color: var(--ok); margin-right: .5rem; }
.quiz-opt.is-wrong { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent); }
.quiz-opt.is-wrong::before { content: "✕"; font-weight: 800; color: var(--danger); margin-right: .5rem; }
.quiz-progress { text-align: center; color: var(--text-dim); font-size: .84rem; font-weight: 700; }

/* --------------------------------- Toasts -------------------------------- */
.toasts {
  position: fixed; left: 50%; top: calc(env(safe-area-inset-top) + .7rem); transform: translateX(-50%); z-index: 80;
  display: flex; flex-direction: column; gap: .4rem; align-items: center; pointer-events: none; width: max-content; max-width: 92vw;
}
.toast {
  display: flex; align-items: center; gap: .45rem;
  background: var(--surface-solid); border: 1.5px solid var(--border); border-radius: 999px;
  padding: .5rem 1rem; font-size: .92rem; font-weight: 700; box-shadow: var(--shadow-2);
  animation: toastIn .22s ease;
}
/* Leaving: a short fade-and-shrink so the stack settles instead of snapping shut when
   the oldest is retired to make room. Collapsing the height as well as the opacity is
   what stops the toasts below it jumping up. */
.toast.is-leaving {
  animation: toast-out .2s ease forwards;
  pointer-events: none;
}
@keyframes toast-out {
  to { opacity: 0; transform: translateY(-6px) scale(.97); max-height: 0; margin-top: -.3rem; }
}
@media (prefers-reduced-motion: reduce) { .toast, .toast.is-leaving { animation: none; } }
[data-motion="reduce"] .toast, [data-motion="reduce"] .toast.is-leaving { animation: none; }
.toast svg { width: 17px; height: 17px; flex: none; }
.toast-good { border-color: color-mix(in srgb, var(--ok) 55%, var(--border)); color: var(--ok); }
.toast-bad { border-color: color-mix(in srgb, var(--danger) 55%, var(--border)); color: var(--danger); }
.toast-warn { border-color: color-mix(in srgb, var(--highlight) 55%, var(--border)); color: var(--highlight); }

/* Applied to the root for a moment when the theme changes, then removed.
   A permanent transition on every surface would fight each component's own hover
   timings and cost a repaint budget nobody asked for; a temporary one costs nothing
   and makes the switch feel like a change of light rather than a redraw. */
.theme-shift, .theme-shift *, .theme-shift *::before, .theme-shift *::after {
  transition: background-color .3s ease, color .3s ease, border-color .3s ease,
              box-shadow .3s ease, fill .3s ease !important;
}

@keyframes fade { from { opacity: 0; } }
@keyframes pop { from { opacity: 0; transform: translateY(10px) scale(.97); } }
@keyframes toastIn { from { opacity: 0; transform: translateY(-8px); } }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
[data-motion="reduce"] *, [data-motion="reduce"] *::before, [data-motion="reduce"] *::after {
  animation-duration: .01ms !important; transition-duration: .01ms !important;
}
