/*
 * hypertype
 *
 * Two rules govern this stylesheet.
 *
 * 1. Nothing competes with the text you are typing. Chrome is dim by default and
 *    disappears entirely the moment you start. No cards, no shadows, no gradients,
 *    no borders that have not earned their keep.
 *
 * 2. The layout is anchored LEFT, not centred, and organised around a vertical
 *    progress rail that runs beside the text. Settings are a labelled row, not a
 *    floating pill, and the live readout stays close to the words without joining
 *    their layout. The palette is cold — cyan on slate — not warm.
 *
 *    That is a deliberate departure. The genre's conventions are worth learning
 *    from; wearing them wholesale is not a design.
 */

/* --------------------------------------------------------------- themes -- *
 *
 * This stylesheet knows nothing about any particular colour scheme. It only ever
 * reads the eight custom properties below, and the layout writes them from the
 * Theme row the typist has chosen (see app/models/theme.rb). Adding a scheme is a
 * database row, not an edit here.
 *
 *   --bg          the page
 *   --bg-raise    the only fill colour (cards, inputs, the rail's track)
 *   --sub         untyped text, and all secondary chrome
 *   --text        text you have typed correctly
 *   --accent      caret, progress, active state
 *   --error       text you got wrong
 *   --error-extra characters typed past the end of a word
 *
 * The values here are a FALLBACK, used only if the themes table is empty — a fresh
 * checkout before db:seed. A blank white page is not an acceptable way to report a
 * missing seed.
 */

:root {
  /* The "paper" palette, mirrored from the seed: the default a visitor sees before
     they have chosen anything. */
  --bg: #e8e5dd;
  --bg-raise: #dbd8cf;
  --sub: #85857b;
  --text: #2b2a25;
  --accent: #0369a1;
  --accent-dim: #7dd3fc;
  --error: #c81e28;
  --error-extra: #e8a0a4;

  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Roboto Mono", Menlo, Consolas, monospace;

  /* The words you are typing are the point of the screen, so they are set large:
     comfortably readable without leaning in, at four lines to a screen.
     The leading is generous on purpose — the gap between lines is where the "what
     you actually typed" mark hangs, and it needs room to be read at a glance rather
     than squinted at. */
  --type-size: 2.15rem;
  --line-height: 4.1rem;
  --visible-lines: 4;    /* the brief: never more than four lines at once */

  --rail: 2px;           /* the vertical progress line beside the text */
  --gutter: 2.25rem;     /* space between that rail and the words */

  /* Headroom above the first line, so the "what you actually typed" mark can hang
     over a mistake without being clipped by the top of the window. Without it, a typo
     on line one would show nothing at all. */
  --ghost-space: 1.9rem;

  /* The vertical band the practice-only finger guide occupies above the words: the
     guide's own rendered height plus the 0.75rem margin it holds above the text.
     The readout has to clear all of it. */
  --hands-space: 5.05rem;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html { height: 100%; }

body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--sub);
  font-family: var(--mono);
  font-size: 19px;      /* everything else is relative to this */
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--sub);
  text-decoration: none;
  transition: color 0.12s ease;
}

a:hover,
a:focus-visible { color: var(--text); }

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

/* ---------------------------------------------------------------- shell -- */

.shell {
  width: 100%;
  max-width: 1520px;   /* the text is the point; give it room */
  margin: 0 auto;
  padding: 2rem 2.5rem 3rem;
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Prose pages read badly at full width — a 1300px line of body copy is a chore to
   track — so only the typing screen takes the extra room. */
.lede,
.auth-form,
.bars,
.advice { max-width: 46rem; }

/* A hairline under the masthead, and the wordmark set flush left with the
   navigation trailing it on the same baseline - not a centred bar. */
.masthead {
  display: flex;
  align-items: baseline;
  gap: 2.5rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--bg-raise);
  transition: opacity 0.25s ease;
}

.logo {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.03em;
}

.logo span { color: var(--accent); }

/* Wraps rather than overflowing. The masthead itself is allowed to wrap on narrow
   screens, but that only moves whole navs onto new lines — the section nav is a single
   flex row, so without this its last item runs off the right edge of a phone and takes
   the whole document into horizontal scroll with it. */
.masthead nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.masthead nav a {
  padding-bottom: 0.15rem;
  border-bottom: 1px solid transparent;
}

.masthead nav a[aria-current="page"] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Everything that is not the text itself gets out of the way once typing
   starts. This is the entire design philosophy in one rule.

   They don't merely fade — they COLLAPSE to nothing, reserving no height. Otherwise
   the practice screen's stack of titles, ladders and pickers would still hold the
   words a thousand pixels down the page while invisible, so the text you are typing
   would sit far below the centre of the screen (or off it) instead of squarely in it.
   With the chrome collapsed, the stage is the only thing left with height, and it
   centres the words on the screen — the same on the test page and the practice one. */
.typing .masthead,
.typing .config,
.typing .controls,
.typing .footnote,
.typing .page-title,
.typing .lede,
.typing .ladder,
.typing .gate-line,
.typing .crumb {
  opacity: 0;
  pointer-events: none;
  max-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
}

/* --------------------------------------------------------------- config -- */

/* A row of labelled dropdowns, flush left. Every setting is the same kind of
   control, and each one names itself: a typist should not have to infer that "60"
   means seconds rather than words. */
.config {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: 0.5rem;
  margin: 1.75rem 0 0;
  padding: 0;
  transition: opacity 0.25s ease;
}

.config-key {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sub);
  opacity: 0.7;
}

.config-divider { display: none; }

/* <details>, not <select>: every option is a real link, so a configuration is
   shareable, bookmarkable, and survives the back button — and none of it needs
   JavaScript. */
.picker {
  position: relative;
  min-width: 8.5rem;
}

.picker summary {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.5rem 2rem 0.5rem 0.85rem;
  background: var(--bg-raise);
  border-left: 2px solid transparent;
  border-radius: 0 0.35rem 0.35rem 0;
  cursor: pointer;
  list-style: none;
}

.picker summary::-webkit-details-marker { display: none; }

/* The little chevron. */
.picker summary::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0.85rem;
  width: 0.4rem;
  height: 0.4rem;
  margin-top: -0.3rem;
  border-right: 1.5px solid var(--sub);
  border-bottom: 1.5px solid var(--sub);
  transform: rotate(45deg);
  transition: transform 0.12s ease;
}

.picker[open] summary::after {
  margin-top: -0.1rem;
  transform: rotate(-135deg);
}

.picker summary:hover,
.picker[open] summary { border-left-color: var(--accent); }

.picker-current {
  color: var(--text);
  font-size: 1rem;
  white-space: nowrap;
}

.picker[open] .picker-current { color: var(--accent); }

.picker-menu {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 100%;
  padding: 0.4rem;
  background: var(--bg-raise);
  border-left: 2px solid var(--accent);
  border-radius: 0 0.35rem 0.35rem 0;
  box-shadow: 0 14px 36px rgb(0 0 0 / 45%);
}

/* The language list in one column would run off the screen. Columns are
   max-content because the options are nowrap: a fixed track (8rem, say) is
   narrower than "Bahasa Indonesia" and the label spills over its neighbour.
   Three columns keeps the whole list visible without scrolling; the max-height
   is the backstop for short viewports.

   The menu stays aligned to its own control. It is wide enough to overhang the
   viewport on the test screen, where language is the last of five controls, so
   picker_controller trims the overhang at the point of opening — see the note
   there for why no static left/right rule works across screens. */
.picker.wide .picker-menu {
  left: 0;
  display: grid;
  grid-template-columns: repeat(3, max-content);
  gap: 0.1rem 0.5rem;
  min-width: 0;
  width: max-content;
  max-height: min(70vh, 30rem);
  overflow-y: auto;
}

.picker-option {
  padding: 0.35rem 0.75rem;
  border-radius: 0.2rem;
  color: var(--sub);
  font-size: 0.95rem;
  white-space: nowrap;
}

.picker-option:hover {
  color: var(--text);
  background: var(--bg);
}

.picker-option.is-active { color: var(--accent); }

/* --------------------------------------------------------------- typing -- */

.typing-app {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* The text and its rail. The rail is the organising element of the whole screen:
   a thin line beside the words that fills as you get through them, so progress is
   something you see in your peripheral vision rather than a number you must read. */
/* The whole stage is the click target — the text, and all the empty space around it.
   Hunting for a line of text to click on before you can start typing is a small,
   stupid friction, and it is the very first thing anyone does on this page. */
.stage {
  flex: 1;
  display: grid;
  grid-template-columns: var(--rail) 1fr;
  column-gap: var(--gutter);
  align-content: center;
  /* A small band beneath the words keeps the last line off the stop button, which is
     pinned to the bottom-left corner. */
  padding: 2rem 0 2.5rem;
  cursor: text;
}

.keycap-actions {
  margin-bottom: 2rem;
  font-size: 0.85rem;
}

/* Exactly as tall as the lines of text beside it, top and bottom.
   The window it sits next to is taller than that — it also holds the headroom the typo
   marks hang in — but that headroom is empty space above the first line, and a rail
   that started up there would read as measuring something the reader cannot see. So the
   rail skips it: dropped by --ghost-space, and shortened by it to match. */
.rail {
  position: relative;
  margin-top: var(--ghost-space);
  height: calc(var(--visible-lines) * var(--line-height));
  background: var(--bg-raise);
  border-radius: 2px;
}

.rail-fill {
  position: absolute;
  inset: 0 0 auto 0;
  height: 0;
  background: var(--accent);
  border-radius: 2px;
  transition: height 0.15s ease;
}

/* The positioning context for the readout, which hangs off the top of this column.
   The only in-flow child is .words-viewport (.hidden-input is absolute), so this box
   is exactly the height of the text window — which is what makes `bottom: 100%` on
   the readout land it on the window's top edge and nowhere else. */
.stage-body {
  position: relative;
  min-width: 0;
}

.words-viewport {
  position: relative;
  height: calc((var(--visible-lines) * var(--line-height)) + var(--ghost-space));
  overflow: hidden;
  cursor: text;
}

.words {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  padding-top: var(--ghost-space);   /* headroom for the typo marks - see --ghost-space */
  font-size: var(--type-size);
  line-height: var(--line-height);
  letter-spacing: 0.005em;
  transition: transform 0.15s ease;
  user-select: none;
}

.word { margin: 0 0.5ch 0 0; }

/* Code keeps its shape. Prose is one long stream that wraps wherever it runs out of
   room; code is a stack of lines whose indentation carries meaning, so each source
   line becomes its own row and only overflows onto a second row when it is genuinely
   too long for the column.

   The gap between words widens to a full 1ch here, from prose's 0.5ch. In a monospace
   face that makes the rendered gap exactly one character wide, which is what makes a
   continuation line indented by four spaces sit under the fourth column of the line
   above it. At 0.5ch every indent would land half a character to the left of the
   thing it is supposed to line up with.

   .line stays unpositioned on purpose: .words is the caret's offset parent in both
   genres, and a positioned wrapper here would silently become the offset parent for
   every character inside it and put the caret a whole line out. */
.words.code {
  display: block;
  white-space: normal;
}

.words.code .line {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  min-height: var(--line-height);   /* a line of only short words still occupies a full row */
}

.words.code .word { margin: 0 1ch 0 0; }

/* Leading whitespace is shown but never typed — Enter jumps the caret past it to the
   first real character of the next line. It is dimmer than the text and carries no
   .char spans, so no scoring path can ever reach it. */
.words.code .indent {
  white-space: pre;
  color: var(--sub);
  opacity: 0.35;
  pointer-events: none;
}

/* A word you fumbled and moved past keeps a quiet underline, so mistakes stay
   legible afterwards without shouting at you mid-test.
   text-decoration, not a border: a border adds 2px to every word's height, which
   pushes the rendered line spacing off the line-height the caret arithmetic
   depends on, and the four-line window then drifts by a line on a long test. */
.word.flawed {
  text-decoration: underline;
  text-decoration-color: var(--error);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
}

.char {
  color: var(--sub);
  transition: color 0.05s ease;
}

.char.correct { color: var(--text); }
.char.extra { color: var(--error-extra); }

/* A mistake shows BOTH characters: the one you were meant to type, in red, in its
   proper place — and above it, small and struck through, the one you actually hit.
   Marking the target red says only that something went wrong. Which key you pressed
   instead is the part you can act on.

   Absolutely positioned, so it hangs in the leading above the line and adds nothing
   to the character's box. That matters: the four-line window and the caret both do
   arithmetic on the line height, and a glyph that grew the line would put them out
   by a row. */
.char.incorrect {
  position: relative;
  color: var(--error);
}

.char.incorrect::before {
  content: attr(data-typed);
  position: absolute;
  left: 50%;
  /* Clear of the tallest ascender, so it sits in the leading rather than on top of
     the letter it is commenting on. */
  bottom: 1.45em;
  transform: translateX(-50%);
  padding: 0 0.2em;
  background: var(--bg);   /* its own ground, so it never merges with the line above */
  font-size: 0.72em;
  line-height: 1.1;
  color: var(--error);
  pointer-events: none;
  white-space: pre;
}

/* An underline caret that sits beneath the next character, rather than a bar
   wedged between two of them. It reads as a cursor in a terminal, which is what
   this app is, and it never crowds the glyphs on either side. */
.caret {
  position: absolute;
  top: 0;
  left: 0;
  width: 1ch;
  height: 2px;
  margin-top: calc(var(--type-size) + 0.35rem);
  background: var(--accent);
  border-radius: 2px;
  transition: left 0.08s ease, top 0.08s ease;
  will-change: left, top;
}

.caret.blinking { animation: blink 1.1s infinite; }

@keyframes blink {
  0%, 45% { opacity: 1; }
  55%, 95% { opacity: 0.15; }
  100% { opacity: 1; }
}

/* The readout hangs off the top edge of the text window, clear of it.
   It used to be positioned from the middle of the *screen*, which is not where the
   text window is centred — the collapsed chrome leaves the stage sitting slightly high
   — so the offset was a fudge that only held at one screen height. Worse, once the
   text scrolled, the top of the window stopped being empty headroom and started
   showing the tail of the line that had just scrolled out, straight under the timer.
   Anchoring to the window itself puts the gap where it is meant to be at any size. */
.readout {
  position: absolute;
  bottom: calc(100% + 0.9rem);
  left: 50%;
  transform: translateX(-50%);
  z-index: 10;
  display: flex;
  align-items: baseline;
  gap: 0.9rem;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}

/* Practice inserts the finger guide between the readout and the words, so clear the
   guide as well as the window: its own height plus the margin it holds above the text. */
.typing-app:has(.hands) .readout {
  bottom: calc(100% + var(--hands-space) + 0.9rem);
}

.typing .readout,
.finished .readout { opacity: 1; }

/* While typing, the chrome is collapsed and the stage owns the whole screen, so drop
   the stage's resting padding (which exists only to hold the words off the corner
   buttons) and let the words centre truly on the screen. */
.typing .stage { padding: 0; }

.readout > span:first-child {
  font-size: 2.6rem;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.readout .secondary {
  font-size: 0.88rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sub);
}

.finished .readout { opacity: 0; }

/* The way out of a test in progress.
 *
 * Deliberately exempt from the rule that hides everything while you type. A test you
 * cannot stop is a trap, and it is worst in the IME section: while an input method has
 * its candidate window open it owns the keyboard, so Escape may never reach the page.
 *
 * Quiet until wanted — barely there at rest, plain on hover — so it costs the typist
 * nothing but is always where they expect it. */
.abort {
  position: fixed;
  left: 2.5rem;
  bottom: 2rem;
  z-index: 30;
  padding: 0.4rem 0.8rem;
  border-radius: 0.3rem;
  color: var(--sub);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0;
  transition: opacity 0.2s ease, color 0.12s ease;
  pointer-events: none;
}

.typing .abort,
.unfocused .abort {
  opacity: 0.45;
  pointer-events: auto;
}

.abort:hover,
.abort:focus-visible {
  opacity: 1;
  color: var(--accent);
  background: var(--bg-raise);
}

.abort kbd {
  padding: 0.05rem 0.3rem;
  border-radius: 0.2rem;
  background: var(--bg-raise);
  font-family: inherit;
  font-size: 0.62rem;
  letter-spacing: 0.05em;
  text-transform: none;
}

.veil {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: none;
  align-items: center;
  justify-content: flex-start;
  /* Derived from the theme rather than hardcoded, or the veil would stay dark on
     a white page. */
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  -webkit-backdrop-filter: blur(3px);   /* Safari needs the prefix until 18 — iPads run older */
  backdrop-filter: blur(3px);
  color: var(--text);
  font-size: 0.9rem;
  letter-spacing: 0.05em;
}

.unfocused .veil { display: flex; }
.unfocused .words { filter: blur(3px); }

/* Opening a settings dropdown legitimately takes focus off the typing input, but
   the typist has not wandered off — they are choosing a setting. Nagging them to
   click back into the text, and blurring it behind the menu they are reading, is
   just noise. */
.typing-app:has(.picker[open]) .veil { display: none; }
.typing-app:has(.picker[open]) .words { filter: none; }

/* The real input. Invisible, but it must remain a first-class, focusable field —
   especially on an iPad, where two iOS behaviours bite:

   font-size MUST be at least 16px. Safari zooms the entire page when a field smaller
   than that takes focus, and on a typing app that means the words lurch and rescale the
   instant you tap to begin.

   And it must not sit behind the page. At z-index: -1 iOS treats it as not really there
   and will not reliably raise the on-screen keyboard — which is the only keyboard an
   iPad has, unless you brought your own. */
.hidden-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  opacity: 0;
  z-index: 0;
  font-size: 16px;
}

/* In the IME section the input is moved under the caret (see moveInput), because an
   input method anchors its candidate window to the focused input. Giving it the height
   of a line means the candidate list opens just below the character being typed, the
   way it does in any other text field — rather than in the corner of the page. */
.ime-section .hidden-input {
  width: 2px;
  height: var(--type-size);
  z-index: 1;
  caret-color: transparent;
}

/* ---------------------------------------------------------------- hands -- *
 *
 * The finger guide, on the practice screen only.
 *
 * It is the one exception to rule 1, and it is worth stating why, because the rule is
 * otherwise absolute. Everything else that fades while you type is ABOUT the session —
 * settings, titles, navigation — and reading any of it means looking away from the
 * words. This is about the next keystroke. It answers the question the words are
 * asking, and it is the only element on the screen that is more useful mid-drill than
 * at rest, so it is the only one that gets brighter rather than disappearing.
 *
 * It earns that by being nearly nothing: ten bars, no outline, no keyboard, no card. A
 * rendered QWERTY board would be the obvious thing to draw here and the wrong one — it
 * invites you to read the layout off the screen, which is looking down at a keyboard by
 * another route, and it is precisely the habit these drills exist to break. Fingertips
 * carry the whole lesson: this hand, that finger.
 */

/* Centred, and above the words rather than beside or below them: the word count is
   pinned to the top centre and the text starts directly underneath, so sitting between
   them puts the count, the finger and the word on one vertical line of sight. Reaching
   for the next key should not cost a glance to the corner and back.

   Centred is a deliberate exception to the layout's left anchor, and the only element
   that takes it. It answers to the readout above it, not to the rail.

   Its own row in the stage grid, spanning rail and body — see shared/_stage. */
.hands {
  grid-column: 1 / -1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 2.25rem;

  /* Clear of the marks that hang above line one — see --ghost-space, which is headroom
     INSIDE .words, so this margin is measured from the top of that headroom. */
  margin-bottom: 0.75rem;
  opacity: 0.75;
  transition: opacity 0.25s ease;
}

/* Dim at rest, plain while typing — the reverse of every other rule in this file. */
.typing .hands { opacity: 1; }

.hand {
  display: flex;
  align-items: flex-end;
  gap: 0.46rem;
}

/* Sized like the fingers they stand for. Bottom-aligned, so it is the TOPS that vary
   and the row of tips draws the arch of a hand — short little, tall middle, back down
   to the index. That arch is the whole reason this reads as a pair of hands rather
   than a bar chart, and it is why the heights are not tidy round numbers. */
.finger {
  width: 0.95rem;
  border-radius: 0.475rem;

  /* Unlit fingers are drawn from --sub rather than --bg-raise. The raise colour is a
     fill for large areas — a rail track, an input — and at the size of a fingertip it
     vanishes into the page on the lighter themes, leaving the lit finger pointing at
     nothing. A silhouette has to be a silhouette in all six palettes. */
  background: var(--sub);
  opacity: 0.25;
  transition: background 0.09s ease, opacity 0.09s ease;
}

.finger[data-finger="little"] { height: 2.7rem; }
.finger[data-finger="ring"] { height: 3.8rem; }
.finger[data-finger="middle"] { height: 4.3rem; }
.finger[data-finger="index"] { height: 3.5rem; }

/* The thumb sits low and inboard of the fingers, where it actually is. */
.finger[data-finger="thumb"] {
  width: 2.3rem;
  height: 0.95rem;
}

.hand [data-hand="left"][data-finger="thumb"] { margin-left: 0.4rem; }
.hand [data-hand="right"][data-finger="thumb"] { margin-right: 0.4rem; }

.finger.is-key {
  background: var(--accent);
  opacity: 1;
}

/* Shift is a different job from striking the key, and it is the job people skip — so
   it is marked, but never as loudly as the finger doing the reaching. */
.finger.is-shift {
  background: var(--accent-dim);
  opacity: 1;
}

/* The key itself, held between the hands: the answer and the hands that give it, read
   in one glance without crossing the screen. */
.hands .hint {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  min-width: 9.5rem;   /* fixed, so a long label never shunts the hands apart */
  padding-bottom: 0.35rem;
}

.hands .hint b {
  font-size: 2.35rem;
  line-height: 1.1;
  color: var(--accent);
  white-space: pre;
}

.hands .hint span {
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--sub);
}

/* A character this layout cannot place — an accented letter, a glyph an input method
   composes. The hands stay, unlit and quiet; nothing is claimed. */
.hands.unknown { opacity: 0.3; }

/* ------------------------------------------------------------- controls -- */

.controls {
  display: flex;
  align-items: center;
  gap: 1.75rem;
  margin-top: 1.75rem;
  min-height: 2rem;
  transition: opacity 0.25s ease;
}

.restart {
  padding-bottom: 0.2rem;
  border-bottom: 1px solid transparent;
  font-size: 0.82rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sub);
}

.restart:hover,
.restart:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.footnote {
  margin-top: 2rem;
  font-size: 0.8rem;
  color: var(--sub);
  transition: opacity 0.25s ease;
}

.footnote kbd {
  padding: 0.1rem 0.35rem;
  border-radius: 0.2rem;
  background: var(--bg-raise);
  font-family: inherit;
  font-size: 0.68rem;
}

/* -------------------------------------------------------------- results -- */

/* Completion becomes a quiet report in the middle of the remaining viewport. The
   settings disappear because the retry controls already provide the next action. */
.results {
  display: none;
  width: 100%;
  max-width: 58rem;
  margin: 0 auto;
  padding: 1.5rem 0 0;
}

.finished .results {
  display: block;
  animation: rise 0.25s ease;
}

.finished .stage { display: none; }

.finished .typing-app {
  justify-content: center;
}

.finished .typing-app > .config,
.finished .typing-app > .page-title,
.finished .typing-app > .lede,
.finished .typing-app > .ladder,
.finished .typing-app > .gate-line,
.finished .typing-app > .crumb {
  display: none;
}

.finished .typing-app > .controls {
  justify-content: center;
  margin-top: 0.75rem;
}

/* The shortcut hints belong to the typing stage. On the results screen the two
   buttons above already say what they do, so the line is just noise. */
.finished .typing-app > .footnote { display: none; }

@keyframes rise {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: none; }
}

.headline {
  display: flex;
  align-items: baseline;
  gap: 2.5rem;
  margin-bottom: 2rem;
}

.result-score {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 2.75rem;
  margin-bottom: 1.5rem;
}

.result-score .pb-flag {
  align-self: center;
  max-width: 10rem;
}

.metric-label {
  font-size: 0.65rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sub);
}

.metric-value {
  font-size: 5rem;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
}

.metric-value.small {
  font-size: 2.5rem;
  color: var(--text);
}

.result-chart {
  margin: 0 auto 1.75rem;
}

.result-chart svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.result-chart-grid {
  stroke: var(--bg-raise);
  stroke-width: 1;
}

.result-chart-area {
  fill: var(--accent-dim);
  opacity: 0.18;
}

.result-chart-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.result-chart-dot { fill: var(--accent); }

.result-chart figcaption {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  color: var(--sub);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.result-details {
  display: grid;
  grid-template-columns: 1.35fr 0.65fr 1.5fr 1fr 1fr 1fr;
  gap: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--bg-raise);
}

.detail-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 2.5rem;
}

.detail dt {
  font-size: 0.65rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sub);
}

.detail dd {
  font-size: 1.45rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.detail small {
  display: block;
  margin-top: 0.2rem;
  color: var(--sub);
  font-size: 0.58rem;
  line-height: 1.4;
}

.test-type dd {
  display: flex;
  flex-direction: column;
  gap: 0.08rem;
  font-size: 0.9rem;
  line-height: 1.35;
  text-transform: lowercase;
}

.result-weak-keys { grid-column: 1 / -1; }

.result-weak-keys dd {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.35rem;
  font-size: 0.8rem;
}

.result-weak-key {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.25rem 0.5rem;
  border-bottom: 2px solid var(--error);
  border-radius: 0.2rem;
  background: var(--bg-raise);
}

.result-weak-key kbd {
  color: var(--error);
  font: inherit;
}

.result-weak-key small {
  display: inline;
  margin: 0;
}

.pb-flag {
  color: var(--accent);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  min-height: 1rem;
}

/* ----------------------------------------------------- practice + stats -- */

.page-title {
  margin: 2rem 0 0.6rem;
  color: var(--text);
  font-size: 1.2rem;
  letter-spacing: 0.02em;
  transition: opacity 0.25s ease;
}

.lede {
  max-width: 46rem;
  margin-bottom: 1.75rem;
  font-size: 0.95rem;
  line-height: 1.75;
  transition: opacity 0.25s ease;
}

.section-nav {
  display: flex;
  gap: 1.25rem;
  margin: 0 0 1.75rem;
  border-bottom: 1px solid var(--bg-raise);
}

.section-nav a {
  padding: 0.45rem 0;
  color: var(--sub);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.section-nav a[aria-current="page"] {
  margin-bottom: -1px;
  border-bottom: 1px solid var(--accent);
  color: var(--accent);
}

/* The lesson ladder sits directly under the settings row on the practice screen, and
   without a clear gap the two read as one control block. They are not: one chooses
   the material, the other chooses the exercise. */
.ladder {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 2.75rem 0 2.25rem;
  transition: opacity 0.25s ease;
}

.rung {
  flex: 1 1 12rem;
  padding: 0.85rem 1rem;
  border-left: 2px solid var(--bg-raise);
  background: var(--bg-raise);
  border-radius: 0 0.3rem 0.3rem 0;
  text-align: left;
}

.rung:hover { color: var(--text); }
.rung.is-active { border-left-color: var(--accent); }

.rung strong {
  display: block;
  margin-bottom: 0.2rem;
  color: var(--text);
  font-size: 0.9rem;
}

.rung.is-active strong { color: var(--accent); }

.rung span { font-size: 0.75rem; }

.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0;
  margin-bottom: 2.5rem;
  border-left: 1px solid var(--bg-raise);
}

.stat {
  padding: 0.9rem 1.25rem;
  border-right: 1px solid var(--bg-raise);
}

.stat dt {
  margin-bottom: 0.35rem;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sub);
}

.stat dd {
  font-size: 1.9rem;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.stat dd small {
  margin-left: 0.35rem;
  color: var(--sub);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

caption {
  margin-bottom: 0.7rem;
  color: var(--text);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: left;
}

th {
  padding: 0.5rem 0.75rem 0.5rem 0;
  color: var(--sub);
  font-weight: 400;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: left;
  border-bottom: 1px solid var(--bg-raise);
}

td {
  padding: 0.55rem 0.75rem 0.55rem 0;
  color: var(--text);
  border-bottom: 1px solid var(--bg-raise);
  font-variant-numeric: tabular-nums;
}

td.num,
th.num {
  text-align: right;
  padding-right: 0;
}

.keycaps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 2rem;
}

.keycap {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 3rem;
  padding: 0.5rem;
  border-radius: 0.3rem;
  background: var(--bg-raise);
  border-bottom: 2px solid var(--error);
}

.keycap b {
  color: var(--error);
  font-size: 1.2rem;
}

.keycap small {
  color: var(--sub);
  font-size: 0.68rem;
}

.empty {
  padding: 2rem 0;
  font-size: 0.9rem;
}

.badge {
  padding: 0.15rem 0.5rem;
  border-radius: 0.2rem;
  background: var(--accent);
  color: var(--bg);
  font-size: 0.65rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.section { margin-bottom: 2.5rem; }

/* The progress chart. An inline SVG that scales to its box; every colour is a theme
   variable, so it repaints with the palette. A uniform aspect ratio (preserveAspect
   default) keeps the endpoint dots round instead of stretching them into ovals. */
.progress-chart {
  max-width: 48rem;
  margin-bottom: 1rem;
}

.progress-chart svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.chart-area { fill: var(--accent-dim); opacity: 0.22; }

.chart-line {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.chart-avg {
  stroke: var(--sub);
  stroke-width: 1;
  stroke-dasharray: 4 4;
  opacity: 0.7;
}

.chart-best {
  fill: var(--bg);
  stroke: var(--accent);
  stroke-width: 2.5;
}

.chart-now { fill: var(--accent); }

.chart-axis {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.5rem;
  color: var(--sub);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
}

.chart-axis .chart-scale {
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* Pass / missed on a lesson in the history log. Quiet — the number beside it is the
   real story; this only says whether it cleared the gate. */
.verdict {
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.verdict.pass { color: var(--accent); }
.verdict.fail { color: var(--sub); }

/* History is dense tabular data, so its sections need separation and its tables scroll
   instead of forcing the whole page wider than a phone. */
.history-section { margin-top: 2.75rem; }

/* Walking back through a long log. The ends are placeholder spans when there is no
   further page in that direction, so the two links never trade places between pages. */
.pager {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  max-width: 22rem;
  margin-top: 1.5rem;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.pager .pager-end { color: var(--bg-raise); }

.pager .pager-where { color: var(--sub); }

.table-scroll {
  max-width: 100%;
  overflow-x: auto;
}

.table-scroll table { min-width: 38rem; }

/* Deleting a section of your history. Quiet by default — it is not something to invite —
   and only shows its error colour on hover, once you have reached for it deliberately. */
.reset-form { margin-top: 1.5rem; }

.reset-stat {
  padding: 0.4rem 0.85rem;
  border: 1px solid var(--bg-raise);
  border-radius: 0.3rem;
  color: var(--sub);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  transition: color 0.12s ease, border-color 0.12s ease;
}

.reset-stat:hover,
.reset-stat:focus-visible {
  color: var(--error);
  border-color: var(--error);
}

/* The coach's single next step. The one piece of chrome allowed to raise its
   voice, because it is the only thing on the page that tells you what to do. */
.advice {
  max-width: 46rem;
  margin-bottom: 2.5rem;
  padding: 1.1rem 1.5rem;
  border-left: 2px solid var(--accent);
  background: var(--bg-raise);
  border-radius: 0 0.4rem 0.4rem 0;
}

.advice h2 {
  margin-bottom: 0.5rem;
  color: var(--accent);
  font-size: 1.05rem;
}

.advice p {
  margin-bottom: 0.9rem;
  font-size: 0.92rem;
  line-height: 1.75;
}

.advice-action {
  display: inline-block;
  padding-bottom: 1px;
  border-bottom: 1px solid var(--sub);
  color: var(--text);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.advice-action:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* ------------------------------------------------ assessment + courses -- */

.bars {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  max-width: 36rem;
  margin-bottom: 2rem;
}

.bar-row {
  display: grid;
  grid-template-columns: 6.5rem 1fr 3.5rem;
  align-items: center;
  gap: 0.9rem;
  font-size: 0.86rem;
}

.bar-label {
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.bar-track {
  height: 0.35rem;
  border-radius: 2px;
  background: var(--bg-raise);
  overflow: hidden;
}

.bar-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 2px;
}

.bar-fill.weak { background: var(--error); }

.bar-value {
  text-align: right;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.bar-value.weak { color: var(--error); }

.course-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.5rem;
  margin-bottom: 3rem;
}

.course-card {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 1rem 1.1rem;
  background: var(--bg-raise);
  border-left: 2px solid transparent;
  border-radius: 0 0.3rem 0.3rem 0;
}

.course-card:hover { border-left-color: var(--sub); }
.course-card.complete { border-left-color: var(--accent); }

.course-card strong {
  color: var(--text);
  font-size: 1rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.course-card.complete strong { color: var(--accent); }

.course-blurb {
  font-size: 0.82rem;
  line-height: 1.5;
  min-height: 2.2rem;
}

.course-progress {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-top: 0.2rem;
}

.pip {
  width: 1.4rem;
  height: 2px;
  border-radius: 2px;
  background: var(--bg);
}

.pip.lit { background: var(--accent); }

.course-count {
  margin-left: auto;
  font-size: 0.68rem;
  letter-spacing: 0.08em;
}

.lesson-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 3rem;
}

.lesson {
  display: grid;
  grid-template-columns: 2rem 1fr 5rem;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.25rem;
  background: var(--bg-raise);
  border-left: 2px solid transparent;
  border-radius: 0 0.3rem 0.3rem 0;
}

.lesson.locked { opacity: 0.4; }
.lesson.cleared { border-left-color: var(--accent); }
.lesson.open { border-left-color: var(--sub); }

.lesson-number {
  font-size: 1rem;
  color: var(--sub);
  text-align: center;
}

.lesson.cleared .lesson-number { color: var(--accent); }

.lesson-body {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.lesson-body strong {
  color: var(--text);
  font-size: 1rem;
}

.lesson-aim {
  font-size: 0.86rem;
  line-height: 1.6;
}

.lesson-gate {
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  color: var(--accent);
}

.lesson.locked .lesson-gate { color: var(--sub); }

.lesson-action {
  text-align: right;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.locked-note {
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sub);
}

.gate-line {
  margin-bottom: 1rem;
  font-size: 0.78rem;
  color: var(--sub);
  transition: opacity 0.25s ease;
}

.gate-line strong { color: var(--accent); }

.crumb { color: var(--sub); }

.verdict {
  min-height: 1.5rem;
  margin-top: 1.25rem;
  padding-left: var(--gutter);
  font-size: 0.95rem;
}

.verdict.pass { color: var(--accent); }
.verdict.fail { color: var(--error); }

.verdict-next {
  display: inline-block;
  margin-top: 0.4rem;
  margin-left: var(--gutter);
  padding-bottom: 1px;
  border-bottom: 1px solid var(--sub);
  color: var(--text);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.verdict-next:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* ------------------------------------------------------------------ ime -- */

/* CJK characters are dense — far more strokes in the same box than a latin letter —
   so they are set larger, and the line is taller to carry the reading underneath
   each one. */
.ime-section {
  --type-size: 2.7rem;
  --line-height: 5.4rem;
  --visible-lines: 4;
}

.words.cjk {
  display: block;
  word-break: break-all;
  line-height: var(--line-height);
  font-family: var(--mono), "PingFang SC", "Hiragino Sans", "Noto Sans CJK SC",
               "Microsoft YaHei", "Yu Gothic", sans-serif;
  letter-spacing: 0.04em;
}

.words.cjk .char {
  display: inline-block;
  position: relative;
  /* The character's own box is exactly one glyph tall, so "just below the glyph" is a
     position we can actually address. Row spacing still comes from the parent's
     line-height; without this, the box inherits that full height and the reading ends
     up floating somewhere above the character it belongs to. */
  line-height: 1;
  height: 1.15em;   /* a box of known height, so "under the glyph" is addressable */
  vertical-align: baseline;
}

/* The latin keys you press to produce this character: pinyin under a hanzi, romaji
   under a kana or kanji. Without it, a learner who cannot yet read the character has
   no way in at all, and the app would be testing their vocabulary, not their typing.

   Absolutely positioned, so it hangs in the leading below the line and adds nothing
   to the character's box — the caret and the four-line window both do arithmetic on
   the line height, and a glyph that grew the line would put them out by a row. */
.words.cjk .char[data-reading]::after {
  content: attr(data-reading);
  position: absolute;
  left: 50%;
  top: calc(100% + 0.15em);   /* immediately under the glyph's box */
  transform: translateX(-50%);
  font-family: var(--mono);
  font-size: 0.3em;
  line-height: 1;
  letter-spacing: 0;
  color: var(--sub);
  white-space: nowrap;
  pointer-events: none;
}

/* The reading follows the state of its character, so a correctly typed character's
   hint recedes and a mistake's stands out. */
.words.cjk .char.correct[data-reading]::after { opacity: 0.45; }
.words.cjk .char.incorrect[data-reading]::after { color: var(--error); }

/* The IME's provisional buffer: the romaji or pinyin entered but not yet
   converted. It sits under the caret and is never scored. */
.composition {
  position: absolute;
  z-index: 4;
  padding: 0.05rem 0.3rem;
  transform: translateY(calc(var(--line-height) * 0.95));
  color: var(--bg);
  background: var(--accent);
  border-radius: 0.2rem;
  font-size: 0.85rem;
  line-height: 1.4;
  white-space: nowrap;
  pointer-events: none;
}

.composition:empty { display: none; }

/* --------------------------------------------------------------- account -- */

.masthead nav.account {
  margin-left: auto;
  align-items: center;
  gap: 1.25rem;
}

/* The theme picker sits in the masthead, so it is smaller and quieter than the
   settings dropdowns on the typing screen. */
.theme-picker {
  min-width: 0;
}

.theme-picker summary {
  padding: 0.3rem 1.6rem 0.3rem 0.7rem;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.theme-picker summary::after { right: 0.6rem; }

.theme-picker .picker-current {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
}

.theme-picker .picker-menu {
  left: auto;
  right: 0;
  border-left: none;
  border-right: 2px solid var(--accent);
  border-radius: 0.35rem 0 0 0.35rem;
}

/* button_to renders a form, so the option is a <button>: make it lie flat. */
.theme-picker form { display: block; }

.picker-option.swatch {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  text-align: left;
  letter-spacing: 0.04em;
  text-transform: none;
}

/* A miniature of the theme: its page colour, with its caret sitting on it. Far
   more use than the name alone when you are choosing between six of them. */
.swatch-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1rem;
  border: 1px solid;
  border-radius: 0.15rem;
  flex: none;
}

.swatch-chip span {
  display: block;
  width: 2px;
  height: 0.6rem;
  border-radius: 1px;
}

.masthead .cta {
  color: var(--accent);
  border-bottom: 1px solid var(--accent-dim);
}

.masthead .cta:hover { border-bottom-color: var(--accent); }

/* button_to renders a form; make its button read as the link it is. */
.masthead form {
  display: inline;
}

.linkish {
  padding: 0;
  font-size: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--sub);
}

.linkish:hover { color: var(--text); }

.auth-form {
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  max-width: 22rem;
  margin-bottom: 2rem;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.field label {
  font-size: 0.65rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sub);
}

.field input {
  padding: 0.6rem 0.75rem;
  background: var(--bg-raise);
  border: 1px solid transparent;
  border-left: 2px solid var(--bg-raise);
  border-radius: 0 0.3rem 0.3rem 0;
  color: var(--text);
  font: inherit;
  font-size: 1rem;
}

.field input:focus {
  outline: none;
  border-left-color: var(--accent);
}

.hint {
  font-size: 0.7rem;
  color: var(--sub);
}

.checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.78rem;
  color: var(--sub);
  cursor: pointer;
}

.checkbox input { accent-color: var(--accent); }

.submit {
  align-self: flex-start;
  margin-top: 0.5rem;
  padding: 0.6rem 1.5rem;
  background: var(--accent);
  border-radius: 0.3rem;
  color: var(--bg);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 700;
}

.submit:hover { background: var(--text); }

.danger {
  padding: 0.55rem 1.25rem;
  border: 1px solid var(--error);
  border-radius: 0.3rem;
  color: var(--error);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.danger:hover {
  background: var(--error);
  color: var(--bg);
}

.auth-errors {
  padding: 0.75rem 1rem;
  border-left: 2px solid var(--error);
  background: var(--bg-raise);
  border-radius: 0 0.3rem 0.3rem 0;
  color: var(--error);
  font-size: 0.8rem;
}

.flash {
  margin-top: 1.5rem;
  padding: 0.7rem 1rem;
  border-left: 2px solid var(--accent);
  background: var(--bg-raise);
  border-radius: 0 0.3rem 0.3rem 0;
  color: var(--text);
  font-size: 0.82rem;
}

.flash.alert {
  border-left-color: var(--error);
  color: var(--error);
}

/* ----------------------------------------------------------- scratchpad -- */

/* The scratch pad borrows the typing screen's type scale so the two screens feel like
   one app, but nothing else: no rail, no readout, no clock. Its own variables rather
   than --type-size directly, because the test screen sizes its text to fit four lines
   in a window and the pad has no such constraint — they only happen to start equal. */
.scratchpad {
  --pad-size: var(--type-size);
  --pad-line: var(--line-height);

  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Click anywhere in the column, including the empty space below the last line, and
   the caret goes to the text — the same rule the typing stage follows. */
.pad-stage {
  flex: 1;
  display: grid;
  /* An empty first column exactly where the typing screen puts its progress rail, so
     the text on both screens starts on the same vertical line. */
  grid-template-columns: var(--rail) 1fr;
  column-gap: var(--gutter);
  align-content: start;
  padding: 2rem 0 1rem;
  cursor: text;
}

.pad {
  grid-column: 2;
  width: 100%;
  /* Height is set by the controller as you type; the textarea grows with its content
     so the PAGE scrolls, rather than the caret being trapped in an inner scroller. */
  min-height: calc(var(--pad-line) * 4);
  padding: 0;
  border: 0;
  outline: none;
  background: none;
  color: var(--text);
  font-family: var(--mono);
  font-size: var(--pad-size);
  line-height: var(--pad-line);
  letter-spacing: 0.005em;
  caret-color: var(--accent);
  resize: none;
  overflow: hidden;
}

.pad::placeholder {
  color: var(--sub);
  opacity: 0.55;
}

/* The chrome, and the rule that governs it: gone while typing, back when you stop.
   Same behaviour as the config bar on the test screen. */
.pad-foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
  padding: 1rem 0 0.5rem;
  margin-left: calc(var(--rail) + var(--gutter));
  border-top: 1px solid var(--bg-raise);
  transition: opacity 0.4s ease;
}

.scratchpad.typing .pad-foot { opacity: 0; }

/* Still reachable by keyboard while faded — pointer-events would trap a tabbing
   typist on an invisible button. Moving the mouse over it brings it straight back. */
.pad-foot:hover,
.pad-foot:focus-within { opacity: 1 !important; }

.pad-status {
  color: var(--sub);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
}

/* The actions carry .restart, so they inherit the typing screen's size and spacing
   and need no type rules of their own here. */
.pad-actions {
  display: flex;
  gap: 1.5rem;
}

/* Red only once the clear is armed and the label has changed to "really clear?" —
   the colour arrives with the warning, not before it. */
.pad-actions .armed,
.pad-actions .armed:hover {
  color: var(--error);
  border-bottom-color: var(--error);
}

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

/* Tablets — an iPad in portrait is 768-834px wide.
   Wide enough for the full layout, but not for full-size type: at 40px a line holds
   barely a dozen words, and the typist spends the test watching the window scroll
   rather than reading ahead. */
@media (min-width: 721px) and (max-width: 900px) {
  :root {
    --type-size: 1.65rem;
    --line-height: 3.1rem;
  }

  .shell { padding: 1.5rem 1.75rem 2.5rem; }

  /* The masthead carries five nav items and the account controls; it needs to be
     allowed to wrap rather than run off the side. */
  .masthead {
    flex-wrap: wrap;
    gap: 0.75rem 1.5rem;
  }

  .masthead nav.account { margin-left: 0; }

  /* Three columns of language names overflow a tablet. */
  .picker.wide .picker-menu { grid-template-columns: repeat(2, max-content); }
}

@media (max-width: 720px) {
  :root {
    --type-size: 1.2rem;
    --line-height: 2rem;
    --gutter: 1.25rem;
  }

  .shell { padding: 1.25rem 1.25rem 2rem; }

  .masthead {
    flex-wrap: wrap;
    gap: 0.75rem 1.5rem;
  }

  .readout > span:first-child { font-size: 1.6rem; }

  /* The finger guide is hidden on phones, so practice uses the ordinary timer offset. */
  .typing-app:has(.hands) .readout {
    bottom: calc(100% + 0.9rem);
  }

  .metric-value { font-size: 3rem; }

  .headline { gap: 1.5rem; }

  .results { padding-top: 0.75rem; }

  .result-score {
    gap: 1.5rem;
    margin-bottom: 1rem;
  }

  .result-chart { margin-bottom: 1.25rem; }

  .result-chart figcaption span:nth-child(2) { display: none; }

  .result-details {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1.25rem 1rem;
  }

  .test-type { grid-column: 1 / -1; }

  .test-type dd {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.15rem 0.75rem;
  }

  .lesson { grid-template-columns: 1.5rem 1fr; }
  .lesson-action { grid-column: 2; text-align: left; }

  /* A phone types with thumbs, so the finger guide has nothing to teach and no room
     to teach it in. */
  .hands { display: none; }

  /* Two max-content columns of language names are wider than a phone. */
  .picker.wide .picker-menu { grid-template-columns: max-content; }
}

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

/* --------------------------------------------------------- ime practice -- */
/* The practice drill: one word card at a time. The card is centered in a box
   the same height as the test's text window, so the two screens feel like one
   apparatus with two settings. */

.practice-viewport {
  position: relative;
  height: calc((var(--visible-lines) * var(--line-height)) + var(--ghost-space));
  overflow: hidden;
  cursor: text;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.4rem;
  text-align: center;
}

/* Bigger than the test's stream: there is only one word, and the shape of the
   character is part of what is being learned. Line-height leaves room for the
   reading hanging in the leading below each glyph. */
.practice-word.words {
  font-size: 2.4rem;
  line-height: 1.6;
  white-space: nowrap;
}

.practice-word .char { margin: 0 0.06em; }

/* The reading line under the card. Present even when empty so the card does
   not jump when a miss reveals it. */
.practice-gloss {
  min-height: 1.5rem;
  font-size: 0.95rem;
  color: var(--sub);
}

/* The English meaning: always on, but banished to the bottom edge of the
   viewport so it never competes with the character for the eye. Absolutely
   positioned so the card stays dead-centered regardless. */
.practice-meaning {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.6rem;
  padding: 0 2rem;
  font-size: 0.85rem;
  color: var(--sub);
  opacity: 0.75;
}

/* The IME buffer, parked centered under the card rather than chasing a caret:
   there is no caret to chase, only the one word. */
.practice-viewport .composition {
  left: 50%;
  bottom: 10%;
  transform: translateX(-50%);
}

/* The keys lesson scores the buffer, so the chip stops being an accent badge
   and becomes a readout: neutral ground, monospaced, with each letter painted
   as it is pressed. On the accent fill neither verdict colour would read. */
.practice-viewport .composition.guided {
  background: var(--bg-raise);
  color: var(--sub);
  font-family: var(--mono);
  letter-spacing: 0.06em;
}

.composition.guided .ok { color: var(--accent); }
.composition.guided .bad { color: var(--error); }

/* The character those keystrokes belong to. Only its reading brightens — the
   glyph itself is left alone, so the eye is pointed without the card moving. */
.practice-word.words.cjk .char.current[data-reading]::after {
  color: var(--accent);
  opacity: 1;
}

/* A lesson that exists but is not built yet: dimmer than locked, and honest
   about why. */
.lesson.soon { opacity: 0.3; }

/* The mastery meter: how many of the thousand this typist has produced
   correctly at least once. */
.mastery { margin-bottom: 2.5rem; }

.mastery-bar {
  height: 6px;
  background: var(--bg-raise);
  border-radius: 3px;
  overflow: hidden;
}

.mastery-fill {
  display: block;
  height: 100%;
  background: var(--accent);
  border-radius: 3px;
  transition: width 0.4s ease;
}

.mastery-count {
  margin-top: 0.5rem;
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  color: var(--sub);
}

.mastery-count strong { color: var(--accent); }

/* The post-run recap: every fumbled word with its reading and meaning. The one
   part of the results screen that is a lesson rather than a score. */
.practice-recap {
  margin-top: 1.75rem;
  text-align: left;
}

.recap-title {
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sub);
  margin-bottom: 0.6rem;
}

.recap-row {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  padding: 0.3rem 0;
}

.recap-word { font-size: 1.3rem; color: var(--text); }

.recap-pinyin {
  font-family: var(--mono);
  font-size: 0.85rem;
  color: var(--accent);
}

.recap-gloss {
  font-size: 0.85rem;
  color: var(--sub);
}

/* Phrases and sentences on the word card: smaller and allowed to wrap, or a
   twelve-character sentence would march straight out of the viewport. */
.practice-word.words.long {
  font-size: 1.5rem;
  line-height: 2.1;
  white-space: normal;
  max-width: 34rem;
}
