/* Schlaufuchs shared design system (§15). Mobile first: baseline 360px
   portrait, wider layouts only via min-width queries (§5.1). */

/* Self-hosted, no third-party request and no build step. Both faces are OFL;
   see assets/fonts/LICENSE.md. Latin subset only — enough for de + en. */
@font-face {
  font-family: "Grandstander";
  src: url("../fonts/grandstander-var-latin.woff2") format("woff2");
  font-weight: 400 800;
  font-display: swap;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  src: url("../fonts/atkinson-400-latin.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Atkinson Hyperlegible";
  src: url("../fonts/atkinson-700-latin.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* The site is always light and friendly — no dark theme, regardless of the
   device setting (resolved decision, SPEC §18). color-scheme: light keeps
   the browser from darkening form controls and scrollbars. */
:root {
  color-scheme: light;
  /* Colour carries meaning: orange is what you DO, --depth is what you HAVE.
     --ok / --err are momentary answer feedback and nothing else. */
  --orange: #e8590c;
  --orange-soft: #fdc79f;
  --depth: #1f6f8b;
  --depth-soft: #bcdfeb;
  --bg: #fdf6ec;
  --panel: #ffffff;
  --ink: #4a2c17;
  --ink-soft: #8a6a50;
  --ok: #2f9e44;
  --err: #c1121f;
  --err-soft: #ecb6b3;
  --sea: #cfe8f7;
  --grass: #b6d7a8;
  --shadow: 0 2px 8px rgba(74, 44, 23, 0.18);
  --radius: 14px;

  /* ---- Button system (§15) ----
     ONE shape for every button: the same corner (--radius), the same raised
     shadow (--shadow), the same press dip, the same display face. What varies is
     the FILL, and the fill obeys the site's colour law (orange = what you DO,
     --depth = what you HAVE):
       • ACTION  — solid --orange, white text (.primary). One per screen.
       • CHOICE  — white --panel, ink text (.mc/.keypad/.verdict/.tilegrid/
                   .langbtn/.iconbtn). The default tappable tile.
       • INFO    — --depth-soft fill, --depth text (.btn-menu: For parents,
                   Save/Load). Leads to what-you-have / the grown-up's side.
       • SELECTED (a toggle that is on) flips to solid --orange (.langbtn,
                   the sound switch).
       • DESTRUCTIVE arms from a quiet CHOICE tile to solid --err (.resetbtn).
     The one deliberate exception is .ghost — a text-only "no, don't" that must
     not look like a tile, so the real action beside it wins the eye (§10.7).
     A new button picks a role above; it does not invent a sixth look. */
  --btn-press: scale(0.96);      /* the dip every rectangular button shares */
  --btn-press-lg: scale(0.98);   /* full-width buttons dip less, or they jump */

  /* ---- Reward & currency type scale (§3.2, §15) ----
     ⭐ is the site's only currency, so the two little labels that carry it read
     the same everywhere a child meets them, at a size she can actually read on
     the 360px phone this site is built for. Colour follows the site's law:
       • --fs-trophy-name — a trophy's name under its cup, on the shelf and in
         the summary. In --depth (a thing you HAVE) when owned, muted when not.
       • --fs-star-note — every "+N ⭐ until the next thing" readout: the locked
         shelf slots and the summary's goal line. In --depth when it is the very
         next thing to chase (the one number worth reading), muted otherwise.
     These grew from 0.62–0.85rem, which a beginning reader could not hold. A
     trophy's name is now set at the same size as the "+N ⭐" it costs — the two
     labels that share a shelf slot read at one size, not two. */
  --fs-trophy-name: 0.95rem;
  --fs-star-note: 0.95rem;

  /* Joy on top, clarity underneath: a rounded display face for numbers and
     names, a legibility-first face for everything you read. */
  --font-display: "Grandstander", system-ui, sans-serif;
  --font-body: "Atkinson Hyperlegible", system-ui, sans-serif;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

/* ---- graphics registry icons (§ graphics registry) ---- */
.gicon { display: inline-block; vertical-align: middle; line-height: 1; }
img.gicon { object-fit: contain; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  -webkit-tap-highlight-color: transparent;
  /* A round lives in memory until it ends (§10.7), and on Android a downward
     drag near the top of the screen reloads the page — which throws the round
     away without asking. The stage never scrolls, so there is nothing here an
     overscroll could usefully do: no pull-to-refresh, no rubber band, and no
     overscroll history navigation either. The system back gesture is NOT
     covered by this — that one is caught in `leaveguard.js`. */
  overscroll-behavior: none;
}

/* The display face is reserved for names, numbers and headings. Everything
   else — prose, data, URLs — is the body face, and that is a decision, not an
   omission: Atkinson Hyperlegible exists to make `1 l I` and `0 O` impossible
   to confuse, which is exactly what an email address, a URL and a column of
   figures need. So the parents' table and the about page's links stay in it.
   Audited in the browser, not by reading this list: every text-bearing element
   on every page was checked against `getComputedStyle`. */
h1, h2, .question, .region-label, .foxchip .lbl, .chip,
.mc button, .keypad button, .tilegrid button, .primary,
/* the whole button family speaks in one voice: the display face (§15) */
.langbtn, .btn-menu, .resetbtn,
.summary .sline, .feedback-aid,
/* the summary's one button */
.summary button,
/* a trophy's name is a name */
.summary .trophy-earn .won,
/* Scores a child reads are numbers, not data: the star counts on the map, a
   locked trophy's price, the round's tally. In the body face their zeros wear
   Atkinson's hyperlegibility slash, which next to a star reads as "none,
   forbidden" — the right glyph for a URL and the wrong one for a score. */
.region-stars, .trophies .sfoot, .summary .stat,
/* the parents' chips are chips, and its facts are the same equations the game
   sets in the display face */
.pchip, .factlist .fact {
  font-family: var(--font-display);
}

/* keyboard users keep every destination; eyes get the island (§3.1) */
.visually-hidden:not(:focus-within) {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

button {
  font: inherit;
  color: inherit;
  border: none;
  cursor: pointer;
  background: none;
}

/* instant taps: no double-tap zoom delay, no text selection while playing */
button, .iconbtn, .chip, a.region {
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}

:focus-visible {
  outline: 3px solid var(--orange);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ---- top bar (map header & in-game mini header, §3.3) ---- */
.topbar {
  display: flex;
  align-items: center;
  gap: 6px;
  /* Above the round summary (z-index 40), below the sheets that really are
     modal (z-index 50). The summary has one button and no way out written on
     it: the way out is this bar, so the bar must stay lit and tappable while
     the summary is up. A backdrop over it would trap a child in an endless
     succession of rounds. */
  position: relative;
  z-index: 45;
  /* respect the notch/rounded corners: the gear must never be clipped */
  padding: 8px max(10px, env(safe-area-inset-right)) 8px max(10px, env(safe-area-inset-left));
  min-height: 56px;
  /* match the game's 560px column so the header aligns on desktop (§3.3) */
  max-width: 560px;
  margin-inline: auto;
  width: 100%;
}

/* The middle of the bar takes what the two icon buttons leave, and the buttons
   keep their size. Map, game and Pokalraum therefore look identical. */
.topbar .iconbtn { flex: 0 0 auto; }
.topbar .foxchip { flex: 1 1 auto; min-width: 0; overflow: hidden; justify-content: center; }

/* The reader's bar carries a heading where the child's carries the chip: no
   panel, no shadow — a title is not a button (§3.3). */
.topbar .roomtitle {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding-left: 4px;
  font-size: 1.15rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* the map's own map button: same shape, no affordance — you are already here */
.iconbtn.flat {
  background: none;
  box-shadow: none;
  cursor: default;
  opacity: 0.55;
}

.topbar h1 {
  font-size: 1.15rem;
  margin: 0;
}

.iconbtn {
  text-decoration: none;
  color: inherit;
  min-width: 48px;
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: 1.35rem;
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
}

.iconbtn:active { transform: scale(0.95); }

.chip {
  min-height: 48px;
  padding: 0 14px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  white-space: nowrap;
  border-radius: 24px;
  background: var(--panel);
  box-shadow: var(--shadow);
}

a.chip { color: var(--ink); text-decoration: none; }

.game .topbar .chip { padding: 0 10px; font-size: 0.9rem; }

/* The fox chip is a readout, not a control (§3.3). It carries no panel and no
   shadow: it used to look exactly like the .chip buttons beside it and did
   nothing when tapped. */
.foxchip {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 8px 4px 0;
  min-height: 48px;
}
/* Two counters, side by side: the stars, and the trophies they bought. */
.foxchip .lbl {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 0.95rem;
  font-weight: 800;
  white-space: nowrap;
}


/* ---- world map (§3.1) ---- */
/* the island claims the whole viewport below the header; nothing scrolls (§5.1) */
.mappage { height: 100dvh; overflow: hidden; display: flex; flex-direction: column; }
.mappage main { flex: 1; min-height: 0; display: flex; }

/* the sea bleeds past the SVG's own box, so the island floats in water
   instead of sitting in a parchment frame */
.worldmap-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  justify-content: center;
  background: var(--sea);
}

.worldmap {
  width: 100%;
  height: 100%;
  display: block;
}

.worldmap a { cursor: pointer; }

/* A dirt track: sand body, dashed light centre line. Drawn with two strokes
   on one path via paint-order, so each road stays a single element. */
.worldmap .road {
  stroke: #e2c79a;
  stroke-width: 8;
}
.worldmap .roadline {
  stroke: var(--bg);
  stroke-width: 2;
  stroke-dasharray: 6 8;
}

/* Mastered: the road paves itself. Short butt-capped dashes read as separate
   cobbles; a continuous --depth stroke would read as a river (§3.1). */
.worldmap .road.paved {
  stroke: var(--depth);
  stroke-width: 9;
  stroke-dasharray: 2.5 3.5;
  stroke-linecap: butt;
  opacity: 0.9;
}
.worldmap .roadline.paved { display: none; }

/* the village square: bare earth until the village is mastered. Pale stone with
   dashed joints, so it reads as pavement rather than as a pond. */
.worldmap .plaza { fill: none; stroke: none; }
.worldmap .plaza.paved {
  fill: var(--depth-soft);
  stroke: var(--depth);
  stroke-width: 3;
  stroke-dasharray: 2.5 3.5;
}

.worldmap .region-label {
  /* No face is set here: the display list at the top of this file already
     names `.region-label`, and an `inherit` in this heavier selector silently
     won the cascade and put the place names back into the body face — with
     Atkinson's slashed zero in every star count beneath them. */
  font-size: 15px;
  font-weight: 800;
  fill: var(--ink);
  paint-order: stroke;
  stroke: var(--bg);
  stroke-width: 4px;
  stroke-linejoin: round;
  text-anchor: middle;
}

.worldmap .region-stars {
  font-size: 11px;
  font-weight: 700;
  fill: var(--ink);
  text-anchor: middle;
}

/* How many trophies are in the room, written on the room's own wall, above the
   cup it counts (map.js). It used to be a badge under the house, where it read
   as a caption on a map full of captions. */
.worldmap .pokal-count {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 800;
  fill: var(--depth);
  text-anchor: middle;
}

/* star badge tiers (§3.1): 1 = plain, 2 = gold, 3 = glowing gold */
.worldmap .region-badge.badge-t2 .region-stars,
.worldmap .region-badge.badge-t3 .region-stars {
  fill: #b8860b;
  font-weight: 800;
  font-size: 13px;
}
.worldmap .region-badge.badge-t3 {
  filter: drop-shadow(0 0 3px #f4b400);
  animation: badge-glow 2s ease-in-out infinite alternate;
}
@keyframes badge-glow {
  from { filter: drop-shadow(0 0 2px #f4b400); }
  to { filter: drop-shadow(0 0 6px #f4b400); }
}

/* Each region's tap target: one bounded rect, drawn first inside its own <a>,
   large enough for a child's finger (§3.1 wants 64px; the smallest here is
   96×136). Mara tapped the gap between the two village houses and nothing
   happened, because the map was hit-tested by its art alone. The fill is an
   attribute in the markup, not a rule here — a stylesheet that fails to load
   must not leave six black boxes on the island. */
.worldmap .region .hit { cursor: pointer; }

/* Clouds over a region whose game is not built yet (§3.1). They must never
   take the tap: they are unbounded and spill over their neighbours, so the
   region's own `.hit` rect answers instead. The puffs have an edge and a
   shaded underside so they read as weather, not as washed-out art; only the
   veil beneath them is blurred (map.js). */
.worldmap .region .fog { pointer-events: none; }
.worldmap .region .fog .fog-veil { fill: #eef4f6; opacity: 0.42; }
.worldmap .region .fog .cloud-rim { fill: #d7e5ec; }
.worldmap .region .fog .cloud-puff { fill: #fff; }
.worldmap .region.locked .region-label { fill: var(--ink-soft); }

/* Tapping a fogged region shakes its fog instead of leaving the map. */
.worldmap .region .fog.wiggle { animation: fog-wiggle 0.42s ease-in-out 2; }
@keyframes fog-wiggle {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-3px); }
  75% { transform: translateX(3px); }
}

/* …and says so, without a sentence to read. */
.soon-bubble { pointer-events: none; }
.soon-bubble rect { fill: var(--orange); }
.soon-bubble text {
  fill: #fff;
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 800;
}

/* A region's name is a button, so it is drawn on a plate. Without it nothing on
   this map looked pressable and a child tapped the fog. */
.worldmap .label-plate {
  fill: #fff;
  stroke: var(--orange-soft);
  stroke-width: 2;
  filter: drop-shadow(0 2px 2px rgba(74, 44, 23, 0.22));
}

/* What a child may walk into breathes; what is fogged stands still. The global
   reduced-motion switch turns this off — the plate then carries the affordance
   on its own. Without `transform-box` an SVG group pivots on the viewport's
   corner and the region flies off the island. */
.worldmap .region:not(.locked) {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: region-bob 3.2s ease-in-out infinite;
}
.worldmap .region:nth-of-type(even) { animation-delay: -1.6s; }
@keyframes region-bob {
  0%, 100% { transform: translateY(1.5px); }
  50% { transform: translateY(-1.5px); }
}

.worldmap .layer-thriving, .worldmap .layer-mastered { display: none; }
.worldmap .region.thriving .layer-thriving { display: inline; }
.worldmap .region.mastered .layer-thriving,
.worldmap .region.mastered .layer-mastered { display: inline; }

.worldmap .region:hover .region-label,
.worldmap a:focus-visible .region-label { fill: var(--orange); }

.fallback-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  padding: 12px;
}

.fallback-nav a {
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 16px;
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  color: var(--ink);
  font-weight: 700;
  text-decoration: none;
}

.fallback-nav .nav-ico { font-size: 20px; }

footer {
  text-align: center;
  padding: 6px 12px calc(6px + env(safe-area-inset-bottom));
  color: var(--ink-soft);
  font-size: 0.72rem;
}

footer p { margin: 0; }

/* ---- game layout ---- */
/* game screens fit 100dvh without scrolling (§5.1) */
.game {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  max-width: 560px;
  margin: 0 auto;
  overflow: hidden;
}

.journey-wrap { width: 100%; padding: 2px 8px 0; }
.journey { width: 100%; height: auto; display: block; }
.journey .j-node { fill: var(--orange-soft); stroke: var(--orange); stroke-width: 2; }
.journey .j-node.done { fill: var(--ok); stroke: var(--ok); }
/* The region badge's trophy cup, greyed while the region has none yet — the
   Pokalraum's own tease, without a word. */
.region-badge .cup-ghost { opacity: 0.35; filter: grayscale(1); }

/* A drawn tile symbol (the Rechenberg's wall pyramid): sized like the glyph it
   stands beside, coloured by the text it sits in. */
.modeglyph { width: 1.1em; height: 0.95em; }

/* a task that ended after a stumble still earns its waypoint — a red one */
.journey .j-node.missed { fill: #f2b6ba; stroke: var(--err); }
/* obstacle mini-celebrations when the fox passes a milestone (§8.2).
   transform-box/origin are required so scale/rotate pivot on the glyph
   itself, for both <text> and swapped-in <image>. */
.journey .j-obstacle { transform-box: fill-box; transform-origin: 50% 50%; }
.journey .j-obstacle.cleared { opacity: 0.5; }
.journey .j-clear-0 { animation: j-bounce 0.7s ease; }
.journey .j-clear-1 { animation: j-wiggle 0.7s ease; }
.journey .j-clear-2 { animation: j-pop 0.7s ease; }
.journey .j-goal.reached { opacity: 1; animation: goalpop 0.6s ease; }

@keyframes j-bounce {
  0%, 100% { transform: translateY(0); }
  30% { transform: translateY(-8px); }
  60% { transform: translateY(-3px); }
}
@keyframes j-wiggle {
  0%, 100% { transform: rotate(0); }
  25% { transform: rotate(-15deg); }
  50% { transform: rotate(12deg); }
  75% { transform: rotate(-8deg); }
}
@keyframes j-pop {
  0% { transform: scale(1); }
  40% { transform: scale(1.5); }
  100% { transform: scale(1); }
}
.journey .j-fox { transition: transform 0.45s ease; }
.journey .j-fox.hop { animation: hop 0.45s ease; }
.journey .j-fox.stumble { animation: stumble 0.7s ease; }

@keyframes hop { 40% { translate: 0 -7px; } 100% { translate: 0 0; } }
@keyframes stumble {
  20% { translate: -3px 0; } 40% { translate: 3px 0; }
  60% { translate: -2px 0; } 80% { translate: 2px 0; }
}
@keyframes goalpop { 50% { font-size: 30px; } }

/* On a tall phone the leftover space is large. Centring the question splits it
   above and below instead of piling it all into one void (§3.3). */
.stage {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  position: relative; /* anchors the ⚡ moment (§10.6) */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(8px, 2.5vh, 26px);
  /* nudge the group below the optical centre, towards the answers */
  padding: clamp(4px, 7vh, 64px) 14px 4px;
  text-align: center;
}

/* The ⚡ moment (§10.6): one answer at rocket speed. It flies up and fades on
   a TRANSITION, never a keyframe — under reduced motion both are killed and
   the bolt is simply there for a beat and then removed, not hanging mid-air
   (the same contract as the journey's stars, §10.5). */
/* Centred in the empty band above the question, not down in the answer slot
   where it used to sit — there, right after the "=", the bolt read as part of
   the equation. It bursts big and shoots straight up out of the top as it fades,
   a clear "that was fast!" flourish clear of everything a child is reading. Same
   spot in both games (§10.6, §14.4). */
.stage .blitz {
  position: absolute;
  left: 50%;
  top: 7%;
  font-size: 2.8rem;
  pointer-events: none;
  opacity: 1;
  transform: translateX(-50%) scale(1);
  transition: transform 0.6s ease-out, opacity 0.6s ease-out;
}
.stage .blitz.gone {
  opacity: 0;
  transform: translate(-50%, -46px) scale(1.7);
}

/* The aid is the tallest thing the stage ever holds: equation, dot grid and a
   button. Give it the nudge padding back, or the card's foot is clipped. */
.stage:has(#feedback:not([hidden])) {
  padding-top: 8px;
}

/* Always one line: the size below is the wish, `fitQuestion()` in einmaleins.js
   shrinks it per question when a long one ("100 ÷ 10 = 100") would not fit. */
.question {
  width: 100%;
  white-space: nowrap;
  font-size: clamp(2.6rem, 19vw, 5rem);
  font-weight: 800;
  letter-spacing: 0.01em;
  line-height: 1.1;
}

.question .gap {
  color: var(--orange);
  border-bottom: 4px solid var(--orange);
  min-width: 1.2em;
  display: inline-block;
}

/* German divides with a colon, and a colon sits on the baseline. Between two
   40px numerals it reads as "12: three", a label and its value. Lifted to the
   optical middle it reads as an operator (einmaleins.js: `eqHTML`).
   The middle of a digit is roughly cap-height/2 above the baseline; the middle
   of a colon is roughly x-height/2. The lift is the difference, and no more —
   at 0.18em the dots floated above the numbers instead of between them. */
.question .divsign,
.feedback-aid .divsign { vertical-align: 0.08em; }

/* ---- workbook task layouts (§12.1): scaffold, wall, grid ----
   Multi-cell kinds wrap and size themselves — the one-line fitter runs only on
   .qline (rechnungen.js: fitQuestion). A fillable slot is a `.cell`; the one
   being answered wears `.active`, a solved one `.done` — the orange goes away,
   the number is the child's now, and the eye moves to the next "?". */
.question.qmulti { white-space: normal; }

/* A wall or grid is tall, and a SHORT round's scene is nearly square (journey
   width scales with its nodes, height not) — at full width the two together
   overflow the stage, which clips BOTH ends (it centres) and silently squeezes
   the task (a flex item shrinks below its content). While a multi-cell task is
   up: the task never shrinks, the scene yields (capped height, aspect kept,
   centred), and the stage gives up its decorative top padding. */
.question.qmulti { flex: 0 0 auto; }
.stage:has(.qmulti) { padding-top: 8px; }
.stage:has(.qmulti) .journey-wrap { display: flex; justify-content: center; }
.stage:has(.qmulti) .journey { max-height: clamp(80px, 16vh, 140px); width: auto; max-width: 100%; }

/* The numbers the child entered HERSELF stay hers: --depth blue, the colour of
   what you have — clearly apart from the printed ink of the givens. */
.question .cell.done { color: var(--depth); }
.question.qline .cell.done { border-color: transparent; }
/* ONE format per shape. A task that asks a single number (op, gap, ÷R — every
   `.qline` kind) writes its answer in the SAME `.gap` slot einmaleins uses: an
   orange digit on an orange underline, nothing more (Martin, 2026-07-13 — "that
   is plenty"). Only the boxed grids need more, because there every blank is
   already a box and one of them has to say "this is the one you are filling":
   the active cell fills solid with white digits and a bold ring, so the eye
   lands on it at a glance. Each shape keeps the colour of its own blanks: the
   wall fills orange, the grid red (Martin, 2026-07-19) — only the fill differs,
   so the shared shape lives once and each shape names just its colour. */
.q-mauer .cell.active,
.rquad .cell.active { border-style: solid; color: #fff; }
.q-mauer .cell.active { border-color: var(--orange); background: var(--orange); box-shadow: 0 0 0 4px var(--orange-soft); }
.rquad .cell.active { border-color: var(--err); background: var(--err); box-shadow: 0 0 0 4px var(--err-soft); }
/* in a wall/grid an unfilled "?" is a real tap target (§12.1: pick, then type) */
.q-mauer .cell:not(.done),
.q-quad .cell:not(.done) { cursor: pointer; }

/* The number wall: three rows of bricks, every brick the same box so the wall
   reads as one thing. A blank brick is dashed orange until it is filled. */
.q-mauer { font-size: clamp(1.35rem, 7vw, 2rem); }
.q-mauer .mauer { display: grid; gap: 5px; justify-items: center; }
.q-mauer .mrow { display: flex; gap: 5px; justify-content: center; }
.mcell {
  min-width: 3em;
  /* Seat the numeral optically like the grid does (see `.rquad span`): the same
     Grandstander glyphs sit ~2.5px high in the brick, so bias the vertical
     padding downward — more above than below, in em so it tracks the clamped
     font-size. The brick has no min-height, so their sum stays ~equal to keep
     its height; only their split shifts the ink to the true middle. */
  padding: 0.26em 10px 0.06em;
  background: var(--panel);
  border: 2px solid var(--ink-soft);
  border-radius: 10px;
}
/* A given brick is the frame the wall is built on — blue, "what is already
   there", the same face the grid gives its headers (user, 2026-07-13). */
.mcell:not(.cell) { background: var(--depth-soft); border-color: var(--depth-soft); color: var(--depth); }
.mcell.cell { border-style: dashed; border-color: var(--orange); }
.mcell.cell.done { border-style: solid; border-color: var(--depth-soft); }

/* The operation grid: the op in the corner, headers on blue, cells as boxes —
   the same table the workbook draws, two by two. */
.q-quad { font-size: clamp(1.3rem, 6.5vw, 1.9rem); }
.q-quad .rquad {
  display: grid;
  grid-template-columns: repeat(3, minmax(3.1em, auto));
  gap: 4px;
  justify-content: center;
}
.rquad span {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 1.55em;
  /* Seat the numeral optically, not geometrically (Martin, 2026-07-19). The
     flex box centres the whole LINE box, but Grandstander's digits and "?" sit
     ~16-17px above the baseline and barely 0-1px below it (measured), so their
     ink centres ~0.2em above the line-box centre — the glyph reads high. Bias
     the padding the other way — more above than below, in em so it tracks the
     clamped font-size — to drop the ink to the true middle. line-height:1 first
     strips the natural leading the box would otherwise centre around. */
  line-height: 1;
  padding: 0.32em 8px 0.1em;
  border-radius: 10px;
}
/* One frame, three states, told by colour (Martin, 2026-07-19):
   • the RAND — corner + the header row/column — is always blue: the scaffold
     that is simply there, the same blue as everything the child already has;
   • a KNOWN inner value is white with a blue border, whether it was printed or
     the child solved it — a filled cell reads the same as a given one;
   • a BLANK still to fill is the one thing that shouts: red dashed border, a
     red "?", so on Schwer (where headers themselves go blank) the eye still
     finds every hole at a glance instead of hunting orange among orange. */
.rquad .qcorner,
.rquad .qhead { background: var(--depth-soft); color: var(--depth); }
.rquad .qcell { background: var(--panel); border: 2px solid var(--depth-soft); }
.rquad .qgiven { color: var(--ink-soft); }
.rquad .cell { background: var(--panel); border: 2px dashed var(--err); color: var(--err); }
/* A filled cell joins the known values (its blue comes from `.question .cell.done`
   above): the inner one takes a solid blue border, and a filled HEADER also
   restores the blue field of the frame it belongs to — `.rquad .cell` painted it
   white while it was blank. */
.rquad .qcell.cell.done { border-style: solid; border-color: var(--depth-soft); }
.rquad .qhead.cell.done { background: var(--depth-soft); border-color: var(--depth-soft); }

/* Reserving its line keeps the question from jumping when a streak starts, but
   the aid needs every pixel: an empty streak line takes none. */
/* While the aid is up it owns the stage: up to ten rows of dots plus a 48px
   button. The status row yields, whether or not it has anything to say. */
.stage:has(#feedback:not([hidden])) .journey-wrap { display: none; }

/* The round's title: same width as the scene beneath it, so the two read as
   one block rather than as a chip that wandered out of the top bar. */
/* The round's title is also its level picker, and Mara never found it: it read
   as a caption. It now wears a border and a caret and presses like a button.
   The caret is a pseudo-element on purpose — `updateChip()` writes the chip's
   textContent, which would eat a glyph kept in the i18n string. */
.pickheading {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  margin: 0 auto 4px;
  padding: 4px 14px;
  min-height: 44px;
  /* `.stage` is a flex column, so this button is a flex item, and a flex item
     shrinks. It carried `min-height: 0`, and on Schwer — whose keypad is a row
     taller than Leicht's four choice buttons — the stage squeezed it to 16px
     while its content needed 27px: the bottom border cut straight through the
     text. Reported from a phone, invisible on Leicht, invisible in every probe
     that only measured the label. A control does not give up its size. */
  flex: 0 0 auto;
  border: 2px solid var(--orange);
  /* Above the summary (40), so the chip stays a way out of it — Mara tried to
     press it *through* the summary sheet. Below the picker (44): a lit chip
     over the picker's backdrop reads as a button, and pressing it there is a
     dead spot on the sheet. */
  position: relative;
  z-index: 41;
}

/* The caret is drawn, not typed. `content: "▾"` is U+25BE, and on a phone that
   is whatever font the device happens to fall back to — on Android Firefox it
   came out small and sitting on the baseline. Two borders are geometry, and
   geometry renders the same in every engine. */
.pickheading::after {
  content: "";
  flex: 0 0 auto;
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid var(--orange);
}

.pickheading:active { transform: scale(0.97); }

/* The round chip reads "<place> <difficulty> │ <mode>". The separator between
   the difficulty and the mode symbol used to be a middle dot, which a child
   read as a multiplication sign standing between "Leicht" and "＋" (play-test
   2026-07-13, Mara). A drawn vertical rule cannot be mistaken for an operator.
   `.ph-txt` becomes a flex row so the rule sits centred beside the glyphs
   (including the wall's inline `.modeglyph`). */
.pickheading .ph-txt { display: inline-flex; align-items: center; gap: 0.45em; }
.pickheading .ph-sep {
  align-self: stretch;
  width: 2px;
  margin: 0.15em 0;
  border-radius: 1px;
  background: var(--ink-soft);
  opacity: 0.45;
}

/* ---- the round's scene: sky, meadow, basket, fox (§10.5) ---- */
.journey .j-sky { fill: #e8f3fa; }
.journey .j-grass { fill: #bfdfa4; }

/* a slot the star has left, and a slot never filled, look the same: open sky
   above, grey ghost where a collected star used to hang */
.journey .j-ghost { opacity: 0.22; filter: grayscale(1); }

/* The flight. `prefers-reduced-motion` kills every transition site-wide, so
   this degrades to the star simply being in the basket — never to no star. */
.journey .j-star {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  transition: transform 0.75s cubic-bezier(0.34, 0.9, 0.4, 1);
}
.journey .j-star.landed { transform: translate(var(--dx), var(--dy)) scale(0.52); }
.journey .j-star.j-instant { transition: none; }

/* What a slot counts (§10.2) is no longer written on it. Mittel and Schwer pay
   double and triple, so a slot simply holds two stars, or three, and they fly
   into the basket together (`starCluster` in journey.js). A "×3" tag was a
   sentence, and it was aimed at a child who cannot read one. */

.feedback-aid {
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 12px 16px;
  font-size: 1.3rem;
  font-weight: 800;
  min-width: min(240px, 78vw);
}

.feedback-aid .eq { color: var(--ok); font-size: 1.6rem; display: block; }

/* What the child wrote, retracted: red, struck through, and a size smaller than
   the truth below it. Mara pressed "Verstanden" without noticing she had erred;
   she never saw her own answer next to the right one. */
.feedback-aid .eq-wrong {
  color: var(--err);
  font-size: 1.15rem;
  background: #fdecee;
  border-radius: 8px;
  padding: 1px 8px;
  margin-bottom: 2px;
}
/* the number she got wrong, made the loudest thing on the card */
.feedback-aid .eq .ans {
  color: var(--ok);
  background: #e4f5e8;
  border-radius: 6px;
  padding: 0 5px;
}

/* the gap she fills to move on — the same gap the question wears */
.feedback-aid .gap {
  display: inline-block;
  margin-top: 8px;
  min-width: 1.6em;
  font-size: 1.6rem;
  color: var(--orange);
  border-bottom: 4px solid var(--orange);
}
.feedback-aid .gap.stumbling,
.question .cell.stumbling,
.mc button.stumbling { animation: stumble 0.7s ease; }

/* Up to ten rows of dots have to fit above the keypad on any phone, so the dot
   scales with the viewport's height rather than sitting at a fixed 9px. */
.dotgrid {
  display: grid;
  gap: clamp(2px, 0.4vh, 3px);
  justify-content: center;
  margin-top: clamp(6px, 1.2vh, 10px);
}

.dotgrid i {
  width: clamp(5px, 1.05vh, 9px);
  height: clamp(5px, 1.05vh, 9px);
  border-radius: 50%;
  background: var(--orange);
}

/* The aid waits for this button, so it has to be a comfortable target. The card
   shrinks to its dot grid, which for `7 × 1` is one dot wide — hence the
   min-width, and a button that sizes to its own text rather than to the card. */

/* ---- inputs: lower half, thumb reach (§5.1) ---- */
.answers {
  padding: 6px 12px calc(10px + env(safe-area-inset-bottom));
}

.mc {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

/* on a tall phone the buttons grow with the screen rather than leaving the
   space empty above them */
.mc button, .keypad button {
  min-height: clamp(52px, 8.5vh, 86px);
  font-size: 1.5rem;
  font-weight: 800;
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
}

.mc button:active, .keypad button:active { transform: scale(0.96); }

.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.keypad .kp-ok { background: var(--ok); color: #fff; }
.keypad .kp-del { font-size: 1.3rem; }

button.flash-ok { background: var(--ok) !important; color: #fff !important; }
button.flash-err { background: var(--err) !important; color: #fff !important; }

/* A lively tap moment on a lesen answer (§14.2): the chosen right one HOPS and
   wears a ✓ badge, the chosen wrong one SHAKES — so picking feels alive, and a
   correct reading answer is celebrated on the tile as well as by the scene emoji
   (§14.2). The classes are only ever added by lesen.js (never einmaleins), so
   they need no container scoping. The green/red flash carries the meaning on its
   own; the hop and shake are decorative keyframes (reduced motion stills them),
   and the ✓ is a ::after badge, not an animation, so it survives reduced motion. */
@keyframes ans-hop { 40% { translate: 0 -8px; } 100% { translate: 0 0; } }
.ans-hop { position: relative; animation: ans-hop 0.42s ease; }
.ans-shake { animation: stumble 0.45s ease; }
.ans-hop::after {
  content: "✓";
  position: absolute;
  top: -7px;
  right: -7px;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  background: var(--ok);
  color: #fff;
  border: 2px solid var(--panel);
  border-radius: 50%;
  font-size: 0.95rem;
  font-weight: 900;
  box-shadow: var(--shadow);
}

/* ---- lesen: the word card and the blitz (§14.2) ---- */
/* The hide is DECIDED by a JS timer and merely decorated by these transitions:
   prefers-reduced-motion kills every transition site-wide, so the fade
   degrades to an instant flip and the mechanic survives. Never a keyframe
   animation here — one that never runs would never hide the word. */
.wordcard {
  position: relative;
  width: 100%;
  min-height: 92px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px 14px;
  background: var(--panel);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.wordcard .question {
  font-size: clamp(2rem, 13vw, 3.4rem);
  transition: opacity 0.2s ease;
}

.wordcard.wc-hidden .question { opacity: 0; }

.wordcard .wordback {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.4rem;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

.wordcard.wc-hidden .wordback { opacity: 1; }

/* The "ready" cover (§14.2): the card's whole face until the child taps it, so
   the blitz never starts before she has looked. A soft depth tint and a 👀 say
   "tap me" without a word to read; it is a real <button>, so it is keyboard-
   reachable. Only shown while .covered — otherwise the word (or ❓) is the face. */
.wordcard .wc-cover {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border: none;
  border-radius: var(--radius);
  background: var(--depth-soft);
  color: var(--depth);
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 700;
}
.wordcard.covered .wc-cover { display: flex; }
.wordcard.covered .question { visibility: hidden; }
.wc-cover-eye { font-size: 2.2rem; line-height: 1; }
.wc-cover-txt { font-size: 1.05rem; }

/* A Mittel sentence is read, not blitzed (§14.1): it wraps, at a size a
   second-grader can hold onto, and the card never turns or hides. */
.wordcard.sent .question {
  white-space: normal;
  font-size: 1.45rem;
  line-height: 1.4;
  font-weight: 700;
  text-align: center;
}

/* Schwer's reading card (§14.2): a passage to read with the question as its last
   sentence, left-aligned and wrapping. No cover, no flip, no blitz — the passage
   stays put the whole time while she reads and answers it. A plain block, not a
   flex column, so the passage and question flow together as one paragraph. */
.wordcard.read {
  display: block;
  /* left-aligned like a book: `.stage` sets text-align:center, and since the
     passage and question run as inline text their alignment comes from this
     block, not from themselves — so it must be reset here, not on them. */
  text-align: left;
  padding: 12px 14px;
}
/* The passage and its question read as ONE flowing paragraph, in the SAME body
   font at the same size and weight (§14.2): the question used to sit on its own
   line in the bold display face, so a child could skim straight to it and answer
   without reading the passage. Run inline as the text's last sentence, she has
   to read down to find it. (The base .question is the display face — this
   overrides it for read.) */
.wordcard .passage,
.wordcard.read .question {
  display: inline;
  font-family: var(--font-body);
  font-size: 1.02rem;
  line-height: 1.4;
  font-weight: 400;
  text-align: left;
  color: var(--ink);
  white-space: normal;
}

/* The picture-book anchor for a Schwer passage (§14.2): a scene emoji floated
   into the text like an illustrated capital, so a reading screen is as inviting
   as the emoji of Leicht — not a wall of grey — and gives a pre-reading anchor
   for what the passage is about. It sets the scene, never the answer (content.js).
   Only on the read card; the attribute-hidden span is display:none everywhere else. */
.wordcard .scene[hidden] { display: none; }
.wordcard.read .scene {
  float: left;
  font-size: 2.6rem;
  line-height: 1;
  margin: 2px 12px 2px 0;
}
/* The warm beat on a correct reading answer (§14.2): a quick happy pop. A
   keyframe, decorative only — prefers-reduced-motion stills it (the rule at the
   foot of the file), and nothing depends on it running. */
@keyframes scene-cheer {
  0% { transform: scale(1); }
  40% { transform: scale(1.28) rotate(-6deg); }
  100% { transform: scale(1); }
}
.wordcard.read .scene.cheer { animation: scene-cheer 0.5s ease; }

/* Four reading answers (§14.2): a single column of Choice tiles whose text
   wraps, so a whole answer sentence fits and reads left-to-right like a list.
   Kept a touch tighter than the emoji buttons so a full passage, its question
   and all four answers share one no-scroll screen with the round's scene. */
.mc-read { grid-template-columns: 1fr; gap: 6px; }
.mc-read button {
  min-height: 44px;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.25;
  text-align: left;
  white-space: normal;
  padding: 7px 14px;
}

/* The journey scene (sky + basket of stars) stays on screen for a reading
   question too, so the child always sees how far she is (§10.5). The word/keypad
   layout pushes its content low with a big top padding (toward the thumb); a
   reading screen has no keypad and wants that room up top for the scene, so here
   the top padding is reclaimed and the scene is drawn large. It steps aside only
   while the wrong-answer aid is up, which every kind shares (below). */
.stage:has(#wordcard.read) { padding-top: 8px; }
/* The scene is drawn large for a reading question (no keypad wants the room),
   but its height is capped to the viewport so a long passage and its four
   answers never get pushed off a short screen: tall on a big phone, smaller on
   a 640px one. `meet` keeps the aspect ratio, so whichever bound binds first
   wins. Sized against the longest passages fitting the 360×640 baseline. */
.stage:has(#wordcard.read) .journey { max-width: 86%; max-height: 18vh; margin-inline: auto; }

/* Four emoji to choose from: the emoji is the label, so it has to be big. */
.mc-emoji button {
  font-size: 2.4rem;
  min-height: clamp(64px, 9.5vh, 96px);
}

/* While a word waits behind the cover (§14.2) the answers are disabled — the
   child can see the four choices, so she knows what is coming, but tapping one
   would be a guess at a word not yet revealed. Dim them so "not yet" is legible,
   not a dead button that merely ignores the finger. */
.answers button:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Stimmt / Quatsch (§14.1): two verdicts, each wearing its symbol in colour —
   a child who reads nothing fluently yet still sees which side is "yes". */
.verdict {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.verdict button {
  min-height: clamp(64px, 10vh, 96px);
  font-size: 1.35rem;
  font-weight: 800;
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.verdict button:active { transform: scale(0.96); }
.verdict .v-sym { font-size: 1.7rem; }
.verdict .v-yes .v-sym { color: var(--ok); }
.verdict .v-no .v-sym { color: var(--err); }

/* the aid's question (§14.2), above the answer she should have chosen */
.feedback-aid .fb-sent {
  display: block;
  font-size: 1.2rem;
  font-weight: 700;
  line-height: 1.4;
  margin-bottom: 6px;
  white-space: normal;
}

/* ---- overlays (§3.3): same page, never separate pages ---- */
.overlay {
  position: fixed;
  inset: 0;
  background: rgba(30, 18, 8, 0.55);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  z-index: 50;
}

.overlay[hidden] { display: none; }

/* While an overlay is open the page behind it is frozen (overlay.js sets this on
   <html>): the Pokalraum shelf used to scroll under the open sheet. */
html.overlay-open, html.overlay-open body { overflow: hidden; }

.sheet {
  background: var(--bg);
  border-radius: 20px 20px 0 0;
  padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
  width: 100%;
  max-width: 560px;
  max-height: 85dvh;
  overflow-y: auto;
}

@media (min-width: 600px) {
  .overlay { align-items: center; }
  .sheet { border-radius: 20px; }
}

.sheet h2 { margin: 0 0 12px; font-size: 1.2rem; }

/* The level picker (§10.2): one scrollable list of every level there is, in
   three colour-coded bands. There used to be a row of difficulty buttons above
   a grid that they rewrote — a child had to understand that the first control
   changed the second, and that "×2 ⭐" on a button she had not pressed was a
   promise about stars she could not see. */
.lvl-head {
  margin: 14px 2px 6px;
  font-size: 0.95rem;
  font-weight: 800;
}
.lvl-head:first-child { margin-top: 0; }
.lvl-head.lvl-easy { color: #2f7d32; }
.lvl-head.lvl-medium { color: #b26a00; }
.lvl-head.lvl-hard { color: var(--err); }

/* Three tiles to a row, not four. Four squeezed a Schwer tile's nine stars and
   its tempo medal into 73px, where six stars read as three and the medal read
   as a smudge (the very complaint that widened them). Three gives every tile
   ~104px, room for a 3×3 star grid at a legible size and a medal that is a
   medal. The list scrolls; legibility is worth the extra height. */
.tilegrid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}

.tilegrid button {
  position: relative;
  overflow: hidden;
  /* tall enough to hold the fullest tile — nine stars in three rows — under its
     name without either crowding the other */
  min-height: 112px;
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  font-weight: 800;
  font-size: 1.2rem;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 9px 6px 6px;
}

/* Easy is green, hard is red, and the middle is between them. The colour says
   which band a tile belongs to without its heading having to be on screen.
   These were three pale washes, and a gold star on pale yellow is not a star —
   the whole promise of the tile was invisible on the band that matters most. */
.tilegrid.lvl-easy button { background: #c3e0b6; }
.tilegrid.lvl-medium button { background: #f4dba4; }
.tilegrid.lvl-hard button { background: #f0c2bf; }

/* The whole point of the picker is to pull a child toward the levels that still
   have stars to give. So the invitation lives on the UNFINISHED tile, not the
   finished one: a warm gold ring (the colour of the stars = the reward) and a
   little extra lift. The mastered tile is the quiet one below — done, faded,
   put away. (Before, the finished tiles wore the loud green frame and the big
   green tick, and the tiles that mattered receded into the band; the child was
   drawn to the work she had already done. User note, 2026-07-13.)
   `.current` and `.mastered` set their own box-shadow further down and both
   sit later in the file at equal specificity, so each overrides this ring. */
.tilegrid button:not(.mastered) {
  box-shadow: inset 0 0 0 2.5px #e6a417, 0 3px 10px rgba(74, 44, 23, 0.22);
}

/* **The stars a tile still has to give**, behind its name — but clustered into
   the ROUNDS that pay them, not spilled loose. A round wins a whole group at
   once: one star on Leicht, a pair on Mittel, a triple on Schwer (§10.2, §10.4).
   So a fresh tile is three groups, always — three rounds to master it — and it
   is the group, not the count, that grows with difficulty. As rounds are won the
   picture empties one whole group at a time (§8.3, §10.2). The groups sit in a
   row; each `.sgroup` stacks its own stars in a tight little column. */
.tilegrid .tstars {
  position: absolute;
  /* below the name, never behind it: a lone star sat exactly under the digits
     and vanished, so a tile with one star left looked like a tile with none */
  inset: 30px 6px 6px;
  pointer-events: none;
  line-height: 1;
  /* the scale from the Wegbild's cluster units (a single star is 28 there) down
     to the tile — one knob for the whole constellation */
  --s: 0.8;
}
/* One group hangs at its anchor (`left`/`top` set inline, from GROUP_ANCHORS);
   it is a zero-size point, and its stars are placed around it. The white edge is
   drawn HERE, on the whole group, not on each star: a filter on the group outlines
   the union of its stars, so a cluster reads as one sticker with a single clean
   halo — where a per-star outline sliced white seams through the overlaps. A hair
   of warm-dark under it keeps the halo from vanishing on the pale-green easy band. */
.tilegrid .sgroup {
  position: absolute;
  width: 0;
  height: 0;
  filter:
    drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff)
    drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)
    drop-shadow(0 1px 1px rgba(120, 80, 20, 0.35));
}
/* Each star sits at its cluster offset from the group's anchor, so a group is the
   exact little constellation the round scene draws — a mini-pyramid on Schwer, a
   leaning pair on Mittel, a lone star on Leicht (`starCluster`, journey.js). */
.tilegrid .tstars i {
  position: absolute;
  left: 0;
  top: 0;
  font-style: normal;
  font-size: calc(var(--sz) * var(--s) * 1px);
  transform: translate(-50%, -50%)
    translate(calc(var(--dx) * var(--s) * 1px), calc(var(--dy) * var(--s) * 1px));
}
/* Nothing left to give — the quiet "done" mark, centred in the star box. It used
   to be a 44px green tick that filled the tile and shouted louder than any tile
   still worth playing; now it is a small, muted stamp that lets the finished tile
   recede (see the invite ring above and `.mastered` below). */
.tilegrid .tstars .tdone {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 30px;
  color: #3f7a49;
  opacity: 0.75;
}

/* The win replay (§10.1, `flyWin` in levelpicker.js): when the picker reopens
   after a round that paid stars, the played tile shows the round back — the
   groups just earned lift off and shrink away, the same fall the round scene's
   sky makes into the basket, while the groups still to win glide to where a
   settled tile sits them. Motion only when it is welcome: under reduced motion
   the tile just opens settled, and `flyWin` is never called, so these transitions
   never have anything to fire on. */
@media (prefers-reduced-motion: no-preference) {
  .tilegrid .sgroup {
    transition: left 0.45s ease, top 0.45s ease;
  }
  .tilegrid .sgroup.won-depart {
    transition: transform 0.5s ease, opacity 0.5s ease;
  }
  .tilegrid .sgroup.won-depart.gone {
    transform: translateY(16px) scale(0.25);
    opacity: 0;
  }
}

/* The tempo badge (§10.6): the pace this tile has been *known* at, worn as a
   little medal in its bottom corner. No white disc around it — the same white
   outline the star groups wear lifts the bare emoji off the busy band, so the
   medal is bigger and plainer, the vehicle itself rather than a plate with a
   vehicle on it. It sits bottom-right, clear of the tile's name at the top.
   There is no tier-0 medal: an empty corner is the lowest state, never a snail. */
.tilegrid .ttempo {
  position: absolute;
  bottom: 3px;
  right: 4px;
  pointer-events: none;
  line-height: 1;
  filter:
    drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff)
    drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff)
    drop-shadow(0 1px 2px rgba(74, 44, 23, 0.32));
}
/* A mastered tile is already faded (`filter: saturate() opacity()` below), and a
   drop-shadow filter nested inside that outer filter speckles the emoji in Chrome.
   The medal needs no white edge on a tile that is meant to recede anyway, so drop
   it there — the fade carries the "done" reading on its own. */
.tilegrid button.mastered .ttempo { filter: none; }

.tilegrid .tname { position: relative; color: var(--ink); }

/* Two states, two looks: mastered pays nothing more, and the level she is
   playing right now is where the list opens. A mastered tile has no invite ring
   and no lift — it is drained of colour and flattened so it sinks behind the
   tiles that still have stars on offer. The green frame it used to wear is gone;
   the small `.tdone` tick above is all that says "done". */
.tilegrid button.mastered {
  box-shadow: none;
  filter: saturate(0.45) brightness(1.04);
  opacity: 0.7;
}
/* One orange ring, not two. The current tile is also the one the picker focuses
   on open, so an inset orange box-shadow sat *inside* the orange `:focus-visible`
   outline — two concentric red rings on the one tile a child looks at first. An
   outline instead of a box-shadow collapses them: `.tilegrid button.current`
   outranks `:focus-visible`, so its ring wins and the focus ring never doubles
   it. `box-shadow: var(--shadow)` also overrides `.mastered`'s green inset ring,
   so a mastered *and* current tile shows the orange one alone, as it did before. */
.tilegrid button.current {
  outline: 3px solid var(--orange);
  outline-offset: -3px;
  box-shadow: var(--shadow);
}

/* The fox stands on the level she is playing (`levelfox.js`). It is positioned
   in the level list's own coordinates, so it scrolls with the tiles rather than
   sailing across them, and the tiles are its offsetParent. */
.levels { position: relative; }

.lvl-fox {
  position: absolute;
  left: 0;
  top: 0;
  /* the fox is inline SVG: a line box would push it down by its own leading,
     and `foxOrigin` has already said where its feet belong */
  line-height: 0;
  pointer-events: none;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, 0.3));
}


/* the points just earned, beside the numbers that earned them */
.summary .gain { color: var(--depth); font-weight: 800; }

.setrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  min-height: 56px;
  border-bottom: 1px solid var(--orange-soft);
}

/* Language: show them all, mark the one you are reading. A row of chips grows
   with the list, so adding Spanish needs no layout change (§6.1). */
.setrow-lang {
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 10px 0;
}

.langpick {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.langbtn {
  flex: 1 1 auto;
  min-height: 52px;
  min-width: 44%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 800;
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
}

.langbtn .flag { font-size: 20px; line-height: 1; }
.langbtn:active { transform: var(--btn-press); }
/* SELECTED: the language you are reading flips to the solid-orange toggle look,
   the same on/off signal the sound switch uses (§15). */
.langbtn[aria-pressed="true"] { background: var(--orange); color: #fff; }

.primary {
  display: block;
  width: 100%;
  min-height: 60px;
  font-size: 1.4rem;
  font-weight: 800;
  border-radius: var(--radius);
  background: var(--orange);
  color: #fff;
  box-shadow: var(--shadow);
  margin-top: 14px;
}

/* the ACTION role's press dip — gentler than the small tiles, so a full-width
   button does not appear to jump (§15) */
.primary:active { transform: var(--btn-press-lg); }

/* The second answer to a question the child did not ask (§10.7). It has to be
   reachable and unmistakable, and it must not compete with the orange one:
   leaving costs stars, staying costs nothing. Same 60px target — a thumb that
   means it must not have to aim. */
.ghost {
  display: block;
  width: 100%;
  min-height: 60px;
  font-size: 1.1rem;
  font-weight: 700;
  border-radius: var(--radius);
  color: var(--ink-soft);
  margin-top: 8px;
}

.leaveask .sheet { text-align: center; }
.leaveask .lg-body { margin: 0; color: var(--ink-soft); }

.summary { text-align: center; }

/* the numbers are for the grown-up looking over the shoulder, not for the
   child — quiet, one line, out of the way of the stars and the trophy */
.summary .stat {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--ink-soft);
  letter-spacing: 0.04em;
}
.summary .best { color: var(--orange); font-weight: 800; }

/* The summary is the one overlay a child meets without having asked for it, and
   the only one with nothing to dismiss it. It therefore stays UNDER the top bar
   (45) and the chip (41), which are the ways out of it. */
#sum-overlay { z-index: 40; }

/* The level picker sits UNDER the top bar (45) too: the map button is the
   one-tap way out of a game, and in the picker nothing is at stake, so the tap
   must go straight through (§3.3, §10.7). But it stays ABOVE the summary (40)
   — "sum-ok" opens the picker while the summary stays open underneath, and the
   picker precedes the summary in the DOM, so at equal z the summary would
   paint over the picker it just opened — and above the chip (41), whose lit
   pill would otherwise sit on the picker's sheet as a dead spot. The truly
   modal sheets (.overlay, 50) still cover all of it. */
#pick-overlay { z-index: 44; }

/* The round's stars as the GROUPS they are won in (§10.1): the same slots the
   sky holds — gold = owned, ghost = still to win, and a freshly won group pops
   in. Under reduced motion the pop is killed and the group is simply there. */
.summary .bigstars { display: flex; justify-content: center; gap: 6px; }
.summary .sslot { width: clamp(76px, 25vw, 112px); aspect-ratio: 1; }
.summary .sslot svg { width: 100%; height: 100%; display: block; }
.summary .sslot.j-ghost { opacity: 0.22; filter: grayscale(1); }
.summary .sslot.fresh svg { animation: slot-pop 0.5s ease; }
@keyframes slot-pop {
  from { transform: scale(0.2); }
  60% { transform: scale(1.12); }
  to { transform: scale(1); }
}
.summary .sline { margin: 6px 0; font-weight: 700; }
/* the next star's price: present, but never louder than the score. Same star
   note as the shelf's locked slots (§15), so the "+N ⭐" a child chases reads
   the same size in the summary as it does in the Pokalraum. */
.summary .sline.goal { font-weight: 400; font-size: var(--fs-star-note); color: var(--ink-soft); }
/* the tempo ladder's verdict (§10.6): a symbol and its name, never seconds */
.summary .sline.tempo { font-size: .95rem; }
.summary .sline.tempo .gicon { vertical-align: -4px; }
.summary .sline.tempo b { color: var(--orange); }
/* A round can win several trophies at once, so this is a row, not a slot. The
   cards inside it carry the panel and the frame (see .tcard); the row is only
   the arrangement. */
.summary .trophy-earn {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin: 10px auto;
  font-weight: 800;
}
/* The card's width, in `rem` — it was `5.5em`, and `.tcard` sets `font-size:
   1.9rem`, so the "5.5em" card came out 167px. A lone won trophy was a big
   white square with a small cup adrift in the middle of it, and two of them
   stacked instead of standing side by side.
   Three at once is the most a round can win (18 points crosses 2, 9 and 18).
   The cup inside is sized by einmaleins.js, which counts the same three cases. */
.summary .trophy-earn .won { width: 10rem; }
.summary .trophy-earn:has(.won:nth-child(2)) .won { width: 8.4rem; }
.summary .trophy-earn:has(.won:nth-child(3)) .won { width: 6.2rem; }

/* ---- Pokalraum / Trophy Room (§3.2) ---- */
/* The page is the room the map sends you to, so it should feel like one: a
   warm wall, and one wooden shelf per game holding what you have won there. */
.trophyroom {
  background:
    linear-gradient(180deg, #fdf1de 0, var(--bg) 260px),
    var(--bg);
}
/* Mara scrolled down the shelves and could not get back out: the way to the map
   had left the screen. The bar follows her down — it is a direct child of the
   scrolling body, so `sticky` needs nothing else — and the room also ends with
   a door. Opaque, in the colour the page's gradient starts with. */
.trophyroom .topbar {
  position: sticky;
  top: 0;
  background: #fdf1de;
}
/* The room announces itself with its symbol, not with its name: the same trophy
   that marks it on the map. A written heading and a paragraph about where
   trophies come from used to live here, and the child this room is for reads
   neither. The name is still on the heading, for the people who listen. */
.roomhead {
  display: flex;
  justify-content: center;
  max-width: 560px;
  margin: 0 auto;
  padding: 6px 14px 2px;
}
.roomhead .roomsymbol {
  margin: 0;
  line-height: 1;
  font-size: 56px;
  filter: drop-shadow(0 3px 5px rgba(74, 44, 23, 0.28));
}

.album-section {
  position: relative;
  max-width: 560px;
  margin: 0 auto 22px;
  padding: 10px 14px 0;
}

/* the shelf board the trophies stand on */
.album-section::after {
  content: "";
  display: block;
  height: 9px;
  margin: 10px 6px 0;
  border-radius: 0 0 5px 5px;
  background: linear-gradient(180deg, #c78a52 0, #a56538 100%);
  box-shadow: 0 4px 7px rgba(74, 44, 23, 0.28);
}

.album-section h2 { font-size: 1.05rem; margin: 4px 0 10px; }

/* `minmax(0, …)`, not a bare `1fr`: a bare fraction floors the track at the
   item's min-content, and the item is a <button> whose min-content is its
   longest unbreakable word. "Rechenschieber" pushed the four columns to 406px
   inside a 360px phone, and the Pokalraum scrolled sideways — which nothing
   else on this site does, and which a fixed overlay inherits: the showcase
   opened 46px off to one side. The name breaks instead. */
.trophies {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
}

/* One trophy card, used on the album shelf and in the round summary. A child
   must recognise the trophy she just won when she finds it on the shelf, so
   both places draw it identically: the cup, her emoji in its bowl, the name. */
.tcard,
.trophies .slot {
  aspect-ratio: 1;
  min-width: 0; /* a grid item floors at min-content otherwise: see .trophies */
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  font-size: 1.9rem;
}

.tcard { text-decoration: none; }

/* The prize stands on the cup, not inside it: a column, symbol above cup, with
   the symbol dipping into the bowl just enough to be *in* it. The pair is
   therefore taller than the cup alone — which is the point. You see what you
   won before you see what it came in. Buried at the glyph's 28 % it was a small
   dark mark in a gold cup, and twelve of those on a shelf are twelve cups. */
.tcard .t-art {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
}

/* The overlap is `em` of the symbol's own size, so it holds at 46px on the
   shelf and at 170px in the showcase without a second number to keep in step. */
.tcard .t-theme {
  position: relative;
  z-index: 1;
  margin-bottom: -0.26em;
  /* a gold bell on a gold cup is one gold blob; the halo separates them */
  filter: drop-shadow(0 0 2px #fff) drop-shadow(0 0 2px #fff) drop-shadow(0 2px 2px rgba(74, 44, 23, 0.3));
}

.tcard .sname,
.trophies .slot .sname {
  font-size: var(--fs-trophy-name);
  font-weight: 700;
  color: var(--ink-soft);
  text-align: center;
  padding: 0 3px;
  /* German compounds do not fit a ~105px slot in one piece (see .trophies), and
     a centred flex item is shrink-to-fit: without `stretch` the name's own box
     is as wide as the word, so it never wraps — it just hangs over both edges. */
  align-self: stretch;
  overflow-wrap: break-word;
  hyphens: auto;
}

.trophies .slot.locked {
  background: transparent;
  box-shadow: none;
  border: 2px dashed var(--depth-soft);
}

/* not yet yours: the shape without the colour */
.trophies .slot.locked .silhouette {
  filter: grayscale(1) contrast(0) brightness(0.72);
  opacity: 0.38;
}

/* How far a locked slot still is, said out loud. It used to live in a `title`
   tooltip, which no child on a phone will ever see — and then in 0.62rem,
   which on the 360px phone this site is designed for she could not read. */
.trophies .slot.locked .sfoot {
  font-size: var(--fs-star-note);
  font-weight: 800;
  color: var(--ink-soft);
  white-space: nowrap;
}
.trophies .slot.locked .sbar {
  display: block;
  width: 62%;
  height: 6px;
  border-radius: 3px;
  background: var(--orange-soft);
  overflow: hidden;
}
.trophies .slot.locked .sbar i {
  display: block;
  height: 100%;
  background: var(--depth);
}

/* What the next trophy still costs, written under its own bar and inside its
   own slot. It used to be a sentence above the shelf, about a trophy three
   slots to the right. It is the one number here worth chasing, so it is stated
   in the ownership colour and not in the muted grey the others wear. */
.trophies .slot.locked .sfoot.snext {
  color: var(--depth);
  font-size: var(--fs-star-note);
  margin-top: 2px;
}

/* what you own is stated in --depth, the ownership colour */
.tcard,
.trophies .slot:not(.locked) { outline: 2px solid var(--depth-soft); }
.tcard .sname,
.trophies .slot .sname { color: var(--depth); }

/* Three to a row at every width, like the level picker (§3.2). The shelf used
   to widen to six on a desktop, back when the phone showed four; three cups a
   row is now the one rhythm the child-facing grids keep, and the 560px column
   the section is capped at holds them big without a second breakpoint. */

/* ---- the showcase: one trophy, held up (§3.2) ---- */
/* Mara won her first trophy, found it on the shelf, tapped it — and it was a
   small square that did nothing. A trophy is for showing to somebody. Tapping it
   fills the screen with it: confetti, a fox jumping beside it, stars blinking
   around it. `prefers-reduced-motion` stops all three (the rule at the foot of
   this file), and what is left is still the trophy, still full size. */
/* The one overlay that is not a form: it is centred, not a sheet slid up from
   the bottom edge, because the trophy is the thing — not a list of settings
   under the thumb. */
.showcase { align-items: center; }
.showcase .sheet {
  text-align: center;
  width: calc(100% - 24px);
  border-radius: 22px;
  padding-bottom: 18px;
  background: radial-gradient(120% 90% at 50% 34%, #fffaf0 0%, var(--bg) 72%);
}

.sc-stage {
  position: relative;
  display: grid;
  place-items: center;
  padding: 18px 0 6px;
  margin-bottom: 10px;
}

/* As big as the screen allows: `--sc-card` is measured in album.js against the
   viewport, because the cup is an emoji sized in px and CSS cannot pick a font
   size from the width of the box it has to fit into. */
/* The plaque is the ONE thing above the rain (z 5): the storm falls in front of
   the whole panel, and only the cup is lifted clear of it, so the confetti
   frames and highlights it instead of burying it (§3.2). */
.sc-card { position: relative; z-index: 6; }
.sc-card .tcard {
  width: var(--sc-card, 210px);
  gap: 8px;
  box-shadow: 0 10px 26px rgba(74, 44, 23, 0.3);
}
.sc-card .tcard .sname { font-size: 1.2rem; padding: 0 10px; }

.sc-sparks {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
}
.sc-sparks i {
  position: absolute;
  font-style: normal;
  font-size: 22px;
  opacity: 0;
  animation: sc-twinkle 1.5s ease-in-out infinite;
}
.sc-sparks i:nth-child(1) { top: 2%; left: 12%; animation-delay: 0s; }
.sc-sparks i:nth-child(2) { top: 12%; right: 10%; animation-delay: 0.2s; }
.sc-sparks i:nth-child(3) { top: 42%; left: 3%; animation-delay: 0.45s; }
.sc-sparks i:nth-child(4) { top: 36%; right: 2%; animation-delay: 0.65s; }
.sc-sparks i:nth-child(5) { bottom: 6%; left: 16%; animation-delay: 0.85s; }
.sc-sparks i:nth-child(6) { top: 0; left: 47%; animation-delay: 1.05s; }
.sc-sparks i:nth-child(7) { bottom: 14%; right: 18%; animation-delay: 1.25s; }

@keyframes sc-twinkle {
  0%, 100% { opacity: 0; transform: scale(0.5) rotate(-12deg); }
  50% { opacity: 1; transform: scale(1.15) rotate(12deg); }
}

/* The cup takes the whole sheet, so the fox stands at its corner and hangs off
   it — beside the prize, never over the name. */
.sc-fox {
  position: absolute;
  right: -8px;
  bottom: -10px;
  z-index: 3;
  line-height: 0;
  animation: sc-hop 0.72s ease-in-out infinite;
}

@keyframes sc-hop {
  0%, 100% { transform: translateY(0); }
  45% { transform: translateY(-18px); }
}

/* ---- confetti ---- */
.confetti-box { position: fixed; inset: 0; pointer-events: none; z-index: 99; overflow: hidden; }
.confetti-box i {
  position: absolute;
  top: -20px;
  display: block;
  border-radius: 2px;
  animation: confetti-fall linear forwards;
}

@keyframes confetti-fall {
  to {
    transform: translateY(105vh) rotate(calc(var(--spin) * 720deg));
  }
}

/* The showcase's endless rain (§3.2): a layer that fills the panel and falls in
   FRONT of everything in it — the cream, the fox, the sparks, the Close button —
   so the storm is loud and impressive. Only the trophy plaque is lifted above it
   (.sc-card, below), so the rain frames and highlights the cup instead of hiding
   it. Each piece fades to nothing the further it falls (a mask over the layer's
   height, so the lower panel and the button under it stay clear), and `--rain-h`
   — the panel's measured height, from confettiRain() — is how far a piece falls,
   so it crosses the visible panel on a sheet of any height. */
.confetti-rain {
  position: absolute;
  inset: 0;
  z-index: 5;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 62%, transparent 97%);
  mask-image: linear-gradient(180deg, #000 0, #000 62%, transparent 97%);
}
.confetti-rain i {
  position: absolute;
  top: 0;
  display: block;
  border-radius: 2px;
  animation: confetti-rain-fall linear infinite;
}
@keyframes confetti-rain-fall {
  0% { transform: translateY(-8%) translateX(0) rotate(0); opacity: 0; }
  7% { opacity: 1; }
  100% {
    transform: translateY(var(--rain-h, 100vh)) translateX(var(--drift)) rotate(calc(var(--spin) * 540deg));
    opacity: 0;
  }
}
/* The panel is the rain's frame — it must be positioned so the layer fills it. */
.showcase .sheet { position: relative; }

/* ---- coming soon stubs ---- */
.stub {
  min-height: 80dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  text-align: center;
  padding: 20px;
}
.stub .stub-emoji { font-size: 4rem; }
.stub h1 { margin: 0; }

/* ---- Privacy page (§3.5) ---- */
/* Long prose, so this is the one page allowed to scroll. Everything else in
   this site fits 100dvh; a legal text that hides half its sentences does not. */
.textpage { min-height: 100dvh; overflow-y: auto; }
.textpage .prose {
  max-width: 40rem;
  margin: 0 auto;
  padding: 8px 16px 32px;
  line-height: 1.55;
}
.textpage .prose .lede { font-size: 1.05rem; color: var(--ink); }
.textpage .prose h2 {
  margin: 22px 0 6px;
  font-size: 1.1rem;
  color: var(--depth);
}
.textpage .prose p { color: var(--ink-soft); }
.textpage .roomtitle { font-size: 1rem; }

/* The footer is two links and nothing else. The sentence that used to sit here
   said what privacy.html says properly, and it cost the map a strip of island. */
.footlinks { display: flex; justify-content: center; gap: 16px; }
.footlink { color: var(--ink-soft); font-size: 0.78rem; text-decoration: underline; }
.roomfoot { text-align: center; padding: 6px 0 18px; }

.textpage .linklist { list-style: none; padding: 0; }
.textpage .linklist li { margin: 0 0 12px; }
.textpage .linklist a { color: var(--depth); font-weight: 700; }
.textpage .linklist span { display: block; font-size: 0.85rem; color: var(--ink-soft); }
.textpage .mail { color: var(--depth); font-weight: 700; }

/* The INFO role of the button system (§15): a raised --depth-soft tile that
   leads to the grown-up's side of the app — For parents, and the backup pair
   below. It carries the SAME shadow and press dip as every other button, so it
   sits in the one plane with them; the blue fill is what says "this goes to what
   you have / the adults", not a flatter, shadowless look. */
.btn-menu {
  border-radius: var(--radius);
  background: var(--depth-soft);
  color: var(--depth);
  box-shadow: var(--shadow);
  font-weight: 800;
}
.btn-menu:active { transform: var(--btn-press-lg); }

/* A line, then the one link out of the sheet: the parents' view, as a button.
   Privacy and About left the gear (they live on the footers); Für Eltern stays,
   set apart from the settings above it by the rule and styled as a real button,
   quieter than the orange primary — it goes to an adult page, not an action. */
.cx-sep {
  border: 0;
  border-top: 1px solid var(--orange-soft);
  margin: 14px 0 4px;
}
.cx-parents.btn-menu {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 52px;
  font-size: 1.1rem;
  text-decoration: none;
}

/* The backup (§9.3) lives adult-side too, under the same rule as Für Eltern:
   two quiet buttons side by side. The armed import turns red like a reset —
   it is one: it replaces the whole cookie. */
.backuprow { display: flex; gap: 8px; margin: 0 0 8px; }
.backuprow .btn-menu {
  flex: 1;
  min-height: 48px;
  font-size: 0.95rem;
  border: 0;
  cursor: pointer;
}
.backuprow .btn-menu.armed { background: var(--err); color: #fff; }
.backupbad { color: var(--err); font-weight: 700; font-size: 0.85rem; margin: 0 0 8px; }

/* ---- Help overlay (§3.3) ---- */
/* The parent's guide for a place, opened by the ❔ button in the bar. A quiet
   grown-up sheet like the settings one: --depth headings (information you HAVE),
   orange kept for the worked example inside each little picture. */
/* The bar's help glyph: a drawn circled "?" in the bar's ink, so it carries the
   same weight as the gear instead of the pale ❔ emoji. */
.iconbtn .helpicon { display: block; color: var(--ink); }
/* The title takes focus on open only to anchor the scroll and the screen reader
   at the top; it is not an interactive control, so it wears no focus ring. */
.help-title:focus { outline: none; }
.help-forparents {
  margin: 0 0 2px;
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--depth);
}
.help-hero { display: flex; justify-content: center; margin: 8px 0 2px; }
.help-ill { width: 100%; max-width: 260px; height: auto; display: block; }
.help-sec { margin: 14px 0 0; }
.help-sec > h3 { margin: 0 0 6px; font-size: 1rem; color: var(--depth); }
.help-sec > p { margin: 0; color: var(--ink-soft); line-height: 1.5; }
.help-steps { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.help-steps li { display: flex; gap: 12px; align-items: center; }
/* The little pictures sit beside their step; a step with no picture just reads
   full width. On the narrowest phones the picture drops above the text. */
.help-art { flex: 0 0 118px; }
.help-art .help-ill { width: 118px; max-width: 118px; }
.help-steptxt { color: var(--ink-soft); line-height: 1.45; font-size: 0.95rem; }
.help-steptxt .help-mode { color: var(--ink); display: block; }
@media (max-width: 400px) {
  .help-steps li { flex-direction: column; align-items: flex-start; gap: 8px; }
  .help-art { flex: 0 0 auto; align-self: center; }
}

/* ---- Parents' view (§14) ---- */
/* A grown-up screen: it may scroll, and it uses --depth for knowledge because
   blue is what you HAVE. Orange stays with the fox and with actions. */
.pchips { display: flex; flex-wrap: wrap; gap: 8px; }
.pchip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--panel); box-shadow: var(--shadow);
  font-weight: 700; font-size: 0.85rem;
}
.textpage .muted { color: var(--ink-soft); font-size: 0.85rem; }

/* Two games report here now: the game's name is the big heading, the section
   headings inside it stay the size they always were. */
.textpage .prose h2.gamehead { font-size: 1.35rem; color: var(--ink); margin: 30px 0 2px; }

/* Every word of the reading game as a small card (§20): the word in the body
   face (it is data a parent scans, not a question a child answers), its state
   as the same colour square the legend wears. */
.wchips { display: flex; flex-wrap: wrap; gap: 6px; margin: 8px 0 10px; }
.wchip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 8px;
  background: var(--panel); box-shadow: var(--shadow);
  font-size: 0.85rem; font-weight: 700;
}
.wchip i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; }

/* Per-game reset in the settings gear (§3.4): one row per game the cookie holds
   progress for, plus an "everything" row — the game named on the left, its reset
   on the right. The armed state turns red, the two-step confirm shown in place
   on the row instead of on one shared icon. */
.setrow-reset { min-height: 0; border-bottom: 0; padding-top: 6px; }
/* The per-game rows are sub-items of the "Delete progress" heading above them,
   so the list is indented under it while the Reset buttons stay flush with the
   sheet's right edge — the heading owns the left margin, its children step in. */
.resetlist { display: flex; flex-direction: column; margin: 0 0 8px; padding-left: 16px; }
.resetrow {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 8px 0; border-top: 1px solid var(--orange-soft);
}
.resetrow:first-child { border-top: 0; }
.resetrow span { color: var(--ink); font-weight: 700; font-size: 0.95rem; }
/* DESTRUCTIVE, at rest a quiet CHOICE tile (§15): the same raised white tile,
   radius and press as the other buttons, only smaller and in muted ink so it
   never reads as the thing to press — until it arms, when it becomes the loud
   solid-red confirm. It used to be a lone brown-outlined pill, the one shape on
   the settings sheet that belonged to no family. */
.resetbtn {
  flex: 0 0 auto; padding: 8px 16px; border-radius: var(--radius);
  border: 0; background: var(--panel); box-shadow: var(--shadow);
  color: var(--ink-soft); font-weight: 800; font-size: 0.85rem; cursor: pointer;
}
.resetbtn:active { transform: var(--btn-press); }
.resetbtn.armed { background: var(--err); color: #fff; }

.ptable { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
.ptable th, .ptable td { padding: 6px 4px; text-align: right; }
.ptable th[scope="row"], .ptable thead th:first-child { text-align: left; }
.ptable thead th { color: var(--ink-soft); font-size: 0.78rem; font-weight: 700; }
.ptable tbody tr { border-top: 1px solid var(--orange-soft); }
.ptable tfoot tr { border-top: 2px solid var(--orange-soft); font-weight: 800; }

/* 10x10 + one header row and column: the times table a parent learned at school */
.heat {
  display: grid;
  grid-template-columns: repeat(11, 1fr);
  gap: 2px;
  max-width: 24rem;
  margin: 8px 0;
}
.heat .hh {
  display: grid; place-items: center;
  font-size: 0.65rem; font-weight: 700; color: var(--ink-soft);
}
.heat .hc { aspect-ratio: 1; border-radius: 3px; }
.h-weak { background: #e8853c; }
.h-open { background: #e6ded2; }
.h-solid { background: var(--depth); }
/* solid AND repeatedly rocket-fast: known by heart, not computed (§20). The
   step darker than --depth; ΔE 20.8 to its neighbour under every CVD type. */
.h-fast { background: #0d3d4f; }

.legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 0.78rem; color: var(--ink-soft); }
.legend i { display: inline-block; width: 11px; height: 11px; border-radius: 3px; vertical-align: -1px; margin-right: 4px; }

.factlist { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; }
.factlist .fact {
  padding: 6px 10px; border-radius: var(--radius);
  background: var(--panel); box-shadow: var(--shadow);
  font-size: 0.9rem;
}
.factlist .fact b { color: var(--depth); }

/* A clickable index (§20): shown only once two games report, it jumps to each
   section — the page grew long enough that a parent should not have to scroll
   past one game to reach another. */
.ptoc {
  display: flex; flex-direction: column; gap: 3px;
  background: var(--panel); box-shadow: var(--shadow);
  border-radius: var(--radius); padding: 10px 14px; margin: 4px 0 10px;
}
.ptoc-h {
  margin: 0 0 3px; color: var(--ink-soft);
  font-size: 0.72rem; font-weight: 800; letter-spacing: 0.05em; text-transform: uppercase;
}
.ptoc a { color: var(--depth); font-weight: 700; text-decoration: none; }
.ptoc a:hover { text-decoration: underline; }
/* so an anchored jump does not tuck the heading under the top of the viewport */
#p-em-sec, #p-lesen-sec, #p-rechnen-sec { scroll-margin-top: 12px; }

/* One line per reading tier (§20): the tier name, then how it is spread across
   the three states. A tally a parent scans, not a wall of chips. */
.tierline { color: var(--ink-soft); margin: 6px 0; }
.tierline b { color: var(--depth); }

/* The rechnungen skill grid (§20): a wide label column and three level columns,
   each cell one folded state square. The times-table heat wears the same
   colours; here the rows are kinds of sums, not factors. */
.skillgrid {
  display: grid;
  grid-template-columns: minmax(5.5rem, 1fr) repeat(3, 2.2rem);
  gap: 4px; align-items: center;
  max-width: 18rem; margin: 8px 0 12px;
}
.skillgrid .sg-h {
  font-size: 0.68rem; font-weight: 700; color: var(--ink-soft); text-align: center;
}
.skillgrid .sg-mode { font-size: 0.85rem; font-weight: 700; color: var(--ink); }
.skillgrid .sg-c { height: 2.2rem; border-radius: 4px; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
