:root {
  /* Committed dark, and saying so is what makes the browser draw its own parts
     to match — the select arrow, the dropdown it opens, scrollbars. Without it
     a phone assumes a light page and paints a near-black arrow onto a
     near-black control, which is invisible. */
  color-scheme: dark;

  /* Ink and celadon. The ground is a warm near-black with green in it rather
     than blue — closer to an ink stone than to a screen — and the accent runs
     青 (qīng, the blue-green of porcelain) into jade. Cinnabar, the red of a
     name chop, is kept for misses and for danger, where a single hot colour on
     a cool page reads instantly. */
  --bg: #0e1416;
  --panel: #161e21;
  --panel-hi: #1e282c;
  --ink: #e9f0ef;
  --muted: #87a09d;
  --ink-soft: #c3d2d0;
  --line: #26343a;
  --qing: #2f9fb5;
  --jade: #4fbf8b;
  --zhu: #d4553f;

  /* A brighter pair of the same two hues, for the two places the app is
     celebrating rather than labelling: the wordmark and a live streak. The
     structural teal and jade above stay muted — they edge panels and fill
     meters, and at that size a bright colour is noise. */
  --qing-lit: #34d3ea;
  --jade-lit: #5cf0a6;
  --lit: linear-gradient(95deg, var(--qing-lit), var(--jade-lit));
  --radius: 14px;
  --card-pad: 22px;

  /* Chinese text needs a stack of its own — a Latin-first stack renders
     simplified characters in whatever fallback the OS picks, which is often a
     Japanese face with visibly wrong glyph forms (直, 骨, 每 all differ). */
  --han: "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC",
         "Noto Sans CJK SC", "Microsoft YaHei", sans-serif;
}

* { box-sizing: border-box; }

/* No tap flash on touch. Every control here answers a press with a colour of
   its own, so the browser's grey rectangle only ever arrives late and lingers. */
button, a, select, [tabindex] { -webkit-tap-highlight-color: transparent; }

/* `hidden` is a UA style, so any author rule that sets `display` beats it.
   Several controls here are flex or grid, where toggling hidden would silently
   do nothing without this. */
[hidden] { display: none !important; }

body {
  margin: 0;
  min-height: 100vh;
  background: radial-gradient(1200px 600px at 50% -10%, #17242a, var(--bg));
  color: var(--ink);
  font: 16px/1.5 "Segoe UI", system-ui, -apple-system, sans-serif;
  display: flex;
  justify-content: center;
  padding: 24px 16px 48px;
}

.app { width: 100%; max-width: 560px; }

/* ---------------------------------------------------------------- topbar */

.topbar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 20px;
}

.brand { display: flex; align-items: baseline; gap: 9px; flex: 1; }
.brand-mark { width: 1.15em; height: 1.15em; font-size: 26px; flex: none; align-self: center; }

.wordmark {
  margin: 0;
  font-family: var(--han);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: 0.02em;
  background: var(--lit);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.wordmark-latin { font-size: 15px; font-weight: 600; color: var(--ink-soft); letter-spacing: 0.02em; }

.settings { display: flex; align-items: flex-end; gap: 8px; }

/* Both fields shrink, and the select inside each shrinks with them. A select
   left at its intrinsic width is as wide as its longest option, so the field
   would narrow while the control inside it did not, and the overflow would land
   on top of the streak guard. */
.field { min-width: 0; display: grid; gap: 4px; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.settings select { width: 100%; }

/* The two selects share the row and shrink together. A flex-basis rather than
   a min-width: a select left to its intrinsic size is as wide as its longest
   option, so the field would narrow while the control inside it did not, and
   the overflow would land on top of the streak guard. The deck gets the larger
   share because deck names are the longest strings here. */
.settings .field { flex: 1 1 0; min-width: 0; }
.field-deck { flex-basis: 9rem; }
.field-direction { flex-basis: 8rem; }

select {
  background: var(--panel);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 7px 10px;
  font-size: 14px;
  text-transform: none;
  letter-spacing: normal;
}

/* Script toggle and streak guard: same height as the selects they sit beside. */
.chip-toggle, .shield {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 34px;
  padding: 0 12px;
  flex: none;
  font: inherit;
  font-size: 13px;
  color: var(--muted);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 9px;
  white-space: nowrap;
  cursor: pointer;
}
.chip-toggle:focus-visible, .shield:focus-visible { outline: 2px solid var(--jade); outline-offset: 2px; }
.chip-toggle { font-family: var(--han); min-width: 44px; }
.chip-toggle:hover { color: var(--ink); border-color: var(--muted); }

.shield svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.7; }
/* Two bars inside the shield. A shield alone says "protected"; the bars are
   what make this the control the warning line calls "pause it". */
.shield .pause { stroke-width: 2; stroke-linecap: round; }
.shield.on { color: var(--qing); border-color: var(--qing); }
.shield.on svg { fill: var(--qing); fill-opacity: 0.18; }

/* Narrow screens keep the icon alone — the label is what makes this too wide
   for a row that already holds two selects. Hidden rather than removed, so the
   button still announces itself as "Streak guard". */
@media (max-width: 520px) {
  .shield { width: 34px; padding: 0; gap: 0; }
  .shield-label {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ------------------------------------------------------------ scoreboard */

.scoreboard { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 12px; }

.stat {
  position: relative;
  overflow: hidden;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 6px;
  text-align: center;
  font: inherit;
  color: var(--ink);
}
.stat[data-tip] { cursor: help; }
.stat[data-tip]:focus-visible { border-color: var(--muted); outline: none; }
button.stat { cursor: pointer; }

/* Progress toward today's goal, drawn along the bottom of its own tile — the
   same device the stage buttons use, so the meaning carries over. */
.stat-fill {
  position: absolute;
  left: 0; bottom: 0;
  height: 3px; width: 0;
  background: linear-gradient(90deg, var(--qing), var(--jade));
  transition: width 0.35s ease;
}

/* The streak carries the wordmark's gradient — it is the number on this row
   worth looking at. Only once there is a streak: a gradient zero would be
   celebrating nothing. Diagonal rather than horizontal, because a single digit
   has no room for a sweep across its width and would read as flat teal. */
.stat-value.lit {
  background: linear-gradient(145deg, var(--qing-lit) 10%, var(--jade-lit) 90%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.stat.met { border-color: var(--jade); }
.stat.met .stat-fill { background: var(--jade); }
.stat.met .stat-value { color: var(--jade); }

.stat-value { display: block; font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; }
.stat-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }

.guard-badge { position: absolute; top: 5px; right: 7px; font-size: 10px; line-height: 1; color: var(--qing); }

.scoreboard-note {
  /* Held open so the row below does not jump as the line comes and goes. */
  min-height: 17px;
  margin: 0 2px 12px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}
.scoreboard-note.warning { color: var(--zhu); }
.scoreboard-note strong { color: var(--ink); font-weight: 600; }

.linkish {
  font: inherit;
  font-size: inherit;
  color: var(--qing);
  background: none;
  border: none;
  padding: 0;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.progress { height: 5px; background: var(--panel); border-radius: 99px; overflow: hidden; margin-bottom: 18px; }
.progress-fill { height: 100%; width: 0; border-radius: 99px; background: linear-gradient(90deg, var(--qing), var(--jade)); transition: width 0.3s ease; }

/* ------------------------------------------------- practice, stages, modes */

.practice { margin-bottom: 10px; }
.practice-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }

.practice-row button {
  display: grid;
  gap: 2px;
  padding: 8px 12px;
  font: inherit;
  text-align: left;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
}
.practice-row button[aria-pressed="true"] { background: var(--panel-hi); border-color: var(--jade); }
.practice-row button:disabled { cursor: not-allowed; color: var(--muted); }
.practice-name { font-size: 13px; font-weight: 600; }
.practice-meta { font-size: 11px; color: var(--muted); }

.stages { margin-bottom: 18px; }
.stage-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.mode-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }

.stage {
  position: relative;
  display: grid;
  gap: 3px;
  padding: 9px 10px 10px;
  font: inherit;
  text-align: left;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.stage[aria-pressed="true"] { background: var(--panel-hi); border-color: var(--jade); }
.stage:disabled { cursor: not-allowed; color: var(--muted); }
.stage-name { font-size: 13px; font-weight: 600; letter-spacing: -0.01em; }
.stage-meta { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.stage:disabled .stage-name { font-weight: 500; }

.stage-fill {
  position: absolute;
  left: 0; bottom: 0;
  height: 3px;
  background: linear-gradient(90deg, var(--qing), var(--jade));
  transition: width 0.3s ease;
}

.unlock-note { margin: 8px 2px 0; font-size: 12px; color: var(--muted); }
.unlock-note strong { color: var(--ink); font-weight: 600; }

.unlock-banner, .goal-banner {
  margin: 0 0 10px;
  padding: 7px 12px;
  font-size: 13px;
  font-weight: 600;
  color: #06110d;
  background: linear-gradient(90deg, var(--qing), var(--jade));
  border-radius: 99px;
}
.goal-banner { background: linear-gradient(90deg, var(--jade), #7ad4a9); }
.goal-banner.warning {
  color: var(--ink);
  background: none;
  border: 1px solid var(--line);
  font-weight: 500;
}

/* ------------------------------------------------------------------ card */

.card {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 28px var(--card-pad) var(--card-pad);
  text-align: center;
}

.direction-hint { margin: 0; font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.12em; }

/* `--prompt-size` is the *unshrunk* size: main.js drops the font-size to keep a
   long phrase on one line, but the line box is sized from this variable rather
   than from the font, so shrinking the text never moves the board. */
.prompt-row { --prompt-size: clamp(34px, 10vw, 50px); margin-bottom: 18px; }
.prompt {
  margin: 10px 0 0;
  font-size: var(--prompt-size);
  line-height: calc(var(--prompt-size) * 1.2);
  font-weight: 700;
  letter-spacing: -0.02em;
  white-space: nowrap;
  min-width: 0;
}
/* Characters want more air between them and less weight than a Latin word. */
.prompt.is-hanzi { font-family: var(--han); font-weight: 500; letter-spacing: 0.04em; }
.prompt-sub { margin: 4px 0 0; min-height: 1.3em; font-size: 15px; color: var(--muted); letter-spacing: 0.02em; }

.choices { display: grid; gap: 10px; }

.choice {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  font: inherit;
  font-size: 17px;
  color: var(--ink);
  text-align: left;
  background: var(--panel-hi);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  transition: transform 0.08s ease, border-color 0.15s ease, background 0.15s ease;
}
.choice .text.is-hanzi { font-family: var(--han); font-size: 22px; letter-spacing: 0.04em; }
.choice:disabled { cursor: default; opacity: 0.75; pointer-events: none; }
.choice:focus { outline: none; }
.choice:focus-visible { outline: 2px solid var(--jade); outline-offset: 2px; }
.choice.correct { background: rgba(79, 191, 139, 0.18); border-color: var(--jade); opacity: 1; }
.choice.wrong { background: rgba(212, 85, 63, 0.18); border-color: var(--zhu); opacity: 1; }

.key {
  flex: none;
  width: 24px; height: 24px;
  display: grid;
  place-items: center;
  font-size: 12px;
  color: var(--muted);
  background: var(--bg);
  border-radius: 7px;
}

.feedback { min-height: 24px; margin: 16px 0 0; font-size: 15px; color: var(--muted); }
.feedback.good { color: var(--jade); font-weight: 600; }
.feedback.bad { color: var(--zhu); }
.feedback .han { font-family: var(--han); }

/* ------------------------------------------------------------ writing pad */

.char-gloss { margin: 12px 0 0; font-size: 26px; font-weight: 700; letter-spacing: -0.01em; }
.char-context { margin: 5px 0 16px; font-size: 14px; color: var(--muted); }
.char-context b { color: var(--ink-soft); font-weight: 500; font-family: var(--han); font-size: 16px; }

/* One square per character, in reading order — the row *is* the word. A
   single-character word gets one big square; a four-character idiom gets four
   smaller ones, because the row has to stay on one line at phone width for the
   word to read as a word rather than as a list. */
.pad-row {
  display: flex;
  gap: 8px;
  justify-content: center;
  align-items: flex-start;
}

/* The squares share the row equally and cap at a comfortable size, so a
   one-character word gets a big square and a four-character idiom four smaller
   ones — without either overflowing the card. Sized from the row rather than
   from the viewport: the card has a max-width, so viewport maths gets it wrong
   on a wide screen. */
.pad-wrap {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  max-width: 300px;
  aspect-ratio: 1;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 12px;
  transition: border-color 0.2s ease, opacity 0.2s ease;
}

/* Where you are in the word. The active square is the only one that takes
   strokes; the ones behind it keep what you wrote, and the ones ahead stay
   quiet so the eye goes to the right place. */
.pad-wrap.is-active { border-color: var(--jade); }
.pad-wrap.is-pending { opacity: 0.45; }
.pad-wrap.is-done { border-color: var(--line); }

/* The 米字格 — the crossed practice grid every Chinese exercise book is ruled
   with. Not decoration: proportion and placement are most of what makes
   handwriting legible, and without guides there is nothing to place strokes
   against. */
.pad-grid {
  position: absolute;
  inset: 10px;
  opacity: 0.5;
  background:
    linear-gradient(to bottom right, transparent calc(50% - 0.5px), var(--line) calc(50% - 0.5px), var(--line) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(to bottom left,  transparent calc(50% - 0.5px), var(--line) calc(50% - 0.5px), var(--line) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(to right,        transparent calc(50% - 0.5px), var(--line) calc(50% - 0.5px), var(--line) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(to bottom,       transparent calc(50% - 0.5px), var(--line) calc(50% - 0.5px), var(--line) calc(50% + 0.5px), transparent calc(50% + 0.5px));
}

.pad { position: absolute; inset: 0; touch-action: none; }
.pad svg { display: block; width: 100%; height: 100%; }

.char-tools { display: flex; gap: 8px; justify-content: center; margin-top: 14px; flex-wrap: wrap; }

/* --------------------------------------------------------------- summary */

.summary h2 { margin: 0 0 6px; font-size: 24px; }
.summary-score { margin: 0 0 14px; font-size: 40px; font-weight: 700; color: var(--jade); }
.summary-list { list-style: none; margin: 0 0 22px; padding: 0; display: grid; gap: 6px; color: var(--muted); }
.summary-list span { color: var(--ink); font-weight: 600; }
.summary-list .han { font-family: var(--han); }

.primary {
  font: inherit;
  font-weight: 600;
  padding: 13px 26px;
  color: #06110d;
  background: linear-gradient(90deg, var(--qing), var(--jade));
  border: none;
  border-radius: var(--radius);
  cursor: pointer;
}

/* ---------------------------------------------------------------- footer */

.footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 18px;
  padding: 0 calc(var(--card-pad) + 1px);
}
.footer-links { display: flex; flex-wrap: wrap; gap: 14px; font-size: 12px; }
.footer-actions { display: flex; align-items: center; gap: 8px; }

.attribution {
  flex-basis: 100%;
  margin: 4px 0 0;
  font-size: 10px;
  line-height: 1.6;
  color: var(--line);
}

.hint { margin: 18px 0 0; font-size: 12px; color: var(--muted); }
.hint.waiting { display: block; color: var(--qing); }
kbd { background: var(--panel-hi); border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px; font-size: 11px; }

.icon-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px; height: 30px;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: 9px;
  cursor: pointer;
}
.icon-toggle:focus-visible { outline: 2px solid var(--jade); outline-offset: 2px; }
.icon-toggle svg { width: 16px; height: 16px; }
.icon-toggle .node { fill: currentColor; stroke: none; }
.icon-toggle .link { fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; }

.share-note { font-size: 12px; color: var(--jade); white-space: nowrap; }
.share-note:empty { display: none; }

/* --------------------------------------------------------------- dialogs */

.confirm {
  max-width: min(330px, calc(100vw - 32px));
  padding: 22px;
  color: var(--ink);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
}
.confirm::backdrop { background: rgba(4, 8, 9, 0.72); }
.confirm h2 { margin: 0 0 8px; font-size: 18px; letter-spacing: -0.01em; }
.confirm-body { margin: 0 0 18px; font-size: 14px; line-height: 1.5; color: var(--muted); }
.confirm-actions { display: flex; gap: 8px; justify-content: flex-end; }
.guard-explainer { font-size: 13px; }

.ghost {
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  background: none;
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 6px 12px;
  cursor: pointer;
}
.ghost:hover { color: var(--ink); border-color: var(--muted); }

.danger {
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  background: var(--zhu);
  border: 1px solid var(--zhu);
  border-radius: 9px;
  padding: 6px 12px;
  cursor: pointer;
}

/* Wider than the confirm dialog: this one holds two code fields, and a code
   wrapped to a 330px column is painful to check by eye before pasting. */
.transfer { max-width: min(420px, calc(100vw - 32px)); }
.transfer .confirm-actions { margin-bottom: 16px; }
.transfer .confirm-actions:last-of-type { margin-bottom: 0; justify-content: space-between; }
.transfer-field { display: block; margin-bottom: 8px; }
.transfer-field > span { display: block; margin-bottom: 6px; font-size: 12px; color: var(--muted); }
.transfer textarea {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 10px;
}
.transfer textarea:focus { outline: none; border-color: var(--jade); }
.transfer-status { margin: 0 0 14px; min-height: 17px; font-size: 12px; }
.transfer-status.good { color: var(--jade); }
.transfer-status.bad { color: var(--zhu); }

/* ------------------------------------------- privacy and terms documents */

/* These inherit the app's tokens so the colours cannot drift apart, but not
   its layout — a document reads left-aligned and needs a measure, where the
   game is centred and narrow. */
.doc {
  max-width: 640px;
  text-align: left;
  font-size: 15px;
  line-height: 1.7;
  color: var(--ink-soft);
}
.doc h1 { margin: 0 0 10px; font-size: 30px; letter-spacing: -0.02em; color: var(--ink); }
.doc h2 {
  margin: 34px 0 8px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
}
.doc p { margin: 0 0 14px; }
.doc ul { margin: 0 0 14px; padding-left: 20px; }
.doc li { margin-bottom: 7px; }
.doc strong { color: var(--ink); font-weight: 600; }
.doc code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  color: var(--jade);
  background: var(--panel);
  border-radius: 5px;
  padding: 1px 5px;
}

.doc-back { margin: 0 0 26px; font-size: 13px; }
.doc-lede { font-size: 17px; line-height: 1.6; color: var(--ink); }
.doc-updated {
  margin-top: 34px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--muted);
}

/* ------------------------------------------------------------------ auth */

/* Google's own button artwork and wording, unaltered — the whole value of the
   standard button is that people recognise it on sight, so it deliberately does
   not match the rest of the page. This is their dark theme, the sanctioned
   variant for a ground like ours.
   https://developers.google.com/identity/branding-guidelines */
.auth { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }

.google-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 36px;
  padding: 0 14px 0 12px;
  font: 500 13px/1 Roboto, system-ui, sans-serif;
  color: #e3e3e3;
  background: #131314;
  border: 1px solid #8e918f;
  border-radius: 18px;
  text-decoration: none;
  white-space: nowrap;
}
.google-btn:focus-visible { outline: 2px solid var(--jade); outline-offset: 2px; }
.g-mark { width: 17px; height: 17px; flex: none; }

.auth-user { display: flex; align-items: center; gap: 8px; }
.auth-name { font-size: 13px; font-weight: 600; }
.auth-note { margin: 0; font-size: 12px; color: var(--zhu); flex-basis: 100%; }

/* Deleting an account is not a peer of the buttons above it, so it does not
   look like one — it sits below the actions as plain text. */
.account-danger-note {
  margin: 18px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  text-align: center;
  font-size: 12px;
}
.account-danger-note .linkish { color: var(--muted); }
.account-danger-note .linkish:hover { color: var(--zhu); }

.confirm-input {
  width: 100%;
  box-sizing: border-box;
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 10px;
}
.confirm-input:focus { outline: none; border-color: var(--jade); }

/* ------------------------------------------------------------ responsive */

/* Phones: the vertical budget is the whole problem. Everything above the
   question is touched once a session; the answers are touched twenty times a
   round. So the chrome gives up space first, and the stage row deliberately
   stays three across — stacking it costs 240px to save nothing. */
@media (max-width: 460px) {
  body { padding: 14px 12px 24px; }
  .field-deck { flex-basis: 7rem; }
  .field-direction { flex-basis: 6.5rem; }
  .topbar { flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 14px; }
  .auth { order: 1; }
  .settings { order: 2; flex-basis: 100%; }

  /* Icon only: the label is what makes this button too wide for a phone, and
     the four-colour G is the part people actually recognise. */
  .google-btn { padding: 0; width: 36px; justify-content: center; }
  .google-btn-label { display: none; }
  .auth-name { display: none; }
  .wordmark { font-size: 22px; }
  .brand-mark { font-size: 22px; }
  .scoreboard { gap: 6px; margin-bottom: 10px; }
  .scoreboard-note { min-height: 0; }
  .scoreboard-note:empty { display: none; }
  .stat { padding: 7px 4px; }
  .stat-value { font-size: 19px; }
  .progress { margin-bottom: 12px; }
  .stages { margin-bottom: 12px; }
  .stage-row, .mode-row { gap: 6px; }
  .stage { padding: 7px 7px 8px; }
  .stage-name { font-size: 12px; }
  .stage-meta { font-size: 10px; }
  .mode-row .stage-name { font-size: 11px; }
  .unlock-note { margin-top: 6px; font-size: 11px; }
  :root { --card-pad: 14px; }
  .card { padding: 20px var(--card-pad) 16px; }
  .choices { gap: 8px; }
  .choice { padding: 12px 14px; font-size: 16px; }
  .footer { margin-top: 12px; }
}

@media (max-width: 400px) {
  .settings { display: grid; grid-template-columns: 1fr 1fr auto auto; gap: 6px; }
  select { width: 100%; font-size: 13px; padding: 6px 8px; }
}

/* Short viewports — an old phone, or any phone in landscape. Height is the
   binding constraint, so this keys off height and trims guidance already read. */
@media (max-height: 600px) {
  .unlock-note { display: none; }
  .scoreboard { margin-bottom: 6px; }
  .stat { padding: 4px; }
  .stat-value { font-size: 17px; }
  .stages { margin-bottom: 8px; }
  :root { --card-pad: 14px; }
  .card { padding: 14px var(--card-pad) 8px; }
  .prompt-row { --prompt-size: clamp(28px, 8vw, 38px); margin-bottom: 12px; }
  .choices { gap: 6px; }
  .choice { padding: 10px 14px; }
  .footer { margin-top: 10px; }
}

/* The hint names keyboard keys a touch device does not have — and on touch the
   round advances on its own, so there is nothing left to say. The waiting line
   survives, since it is the only thing telling you how to carry on. */
@media (pointer: coarse) {
  .hint { display: none; }
  .hint .keys { display: none; }
  .hint.waiting { display: block; }
}

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